Initial Commit

This commit is contained in:
2024-10-12 14:36:36 +02:00
commit bfc5cbf624
67 changed files with 10860 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
/*!
Module `service` provides the canonical implementation of the [ApiService] port. All
user-domain logic is defined here.
*/
use axum_session::SessionAnySession;
use super::models::user::*;
use super::ports::{ApiService, UserNotifier, UserRepository};
pub trait Repository = UserRepository;
pub trait Email = UserNotifier;
#[derive(Debug, Clone)]
pub struct Service<R, N>
where
R: Repository,
N: Email,
{
repo: R,
notifier: N,
}
impl<R, N> Service<R, N>
where
R: Repository,
N: Email,
{
pub fn new(repo: R, notifier: N) -> Self {
Self { repo, notifier }
}
}
impl<R, N> ApiService for Service<R, N>
where
R: UserRepository,
N: Email,
{
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError> {
let result = self.repo.create_user(req).await;
if result.is_err() {
// something went wrong, log the error
// but keep passing on the result to the requester (http server)
return result;
}
let user = result.as_ref().unwrap();
let token = self.repo.create_activation_token(user).await?;
// generate a activation token and send an email
self.notifier.user_created(user, &token).await;
result
}
async fn get_user_session(&self, session: &SessionAnySession) -> Option<User> {
let Some(user_id) = session.get("user") else {
return None;
};
self.repo.find_user_by_id(user_id).await.unwrap_or(None)
}
async fn activate_user_account(
&self,
token: ActivationToken,
) -> Result<User, ActivateUserError> {
let user = match self.repo.find_user_by_activation_token(&token).await {
Ok(u) => u,
Err(e) => return Err(ActivateUserError::Unknown(e.into())),
};
let Some(user) = user else {
return Err(ActivateUserError::NotFound { token });
};
let req = UpdateUserRequest::new().set_verified(Verified::new(true));
let update = self.repo.update_user(&user, req).await;
if let Err(e) = update {
return Err(ActivateUserError::Unknown(e.into()));
}
self.repo.delete_activation_token_for_user(&user).await?;
let (_, user) = update.unwrap();
Ok(user)
}
async fn user_login(&self, req: UserLoginRequest) -> Result<User, UserLoginError> {
let email = req.email();
let user = match self.repo.find_user_by_email(email).await {
Ok(u) => u,
Err(e) => {
tracing::error!("{:#?}", e);
return Err(UserLoginError::Unknown(e.into()));
}
};
let Some(user) = user else {
tracing::warn!("User not found");
return Err(UserLoginError::NotFound {
email: email.clone(),
});
};
if !user.verified().is_verified() {
return Err(UserLoginError::NotActive {
email: email.clone(),
});
}
if let Err(e) = user.password().verify(req.password()) {
tracing::warn!("{:#?}", e);
return Err(UserLoginError::NotFound {
email: email.clone(),
});
}
// Invalidate any reset tokens that might exist
let _ = self.repo.delete_password_reset_tokens_for_user(&user).await;
Ok(user)
}
async fn forgot_password(&self, email: &EmailAddress) {
let user = match self.repo.find_user_by_email(email).await {
Ok(u) => u,
Err(e) => {
tracing::error!("{:#?}", e);
return;
}
};
let Some(user) = user else {
tracing::warn!("User not found");
return;
};
let token = match self.repo.create_password_reset_token(&user).await {
Ok(t) => t,
Err(e) => {
tracing::error!("{:#?}", e);
return;
}
};
self.notifier.forgot_password(&user, &token).await;
}
async fn reset_password(
&self,
token: &PasswordResetToken,
password: &Password,
) -> Result<User, ResetPasswordError> {
let user = match self.repo.find_user_by_password_reset_token(token).await {
Ok(u) => u,
Err(e) => {
tracing::error!("{:#?}", e);
return Err(ResetPasswordError::Unknown(e.into()));
}
};
let Some(user) = user else {
tracing::warn!("User not found");
return Err(ResetPasswordError::NotFound);
};
let req = UpdateUserRequest::new().set_password(password.clone());
self.repo
.update_user(&user, req)
.await
.map_err(|e| ResetPasswordError::Unknown(e.into()))?;
// Invalidate any reset tokens that might exist
let _ = self.repo.delete_password_reset_tokens_for_user(&user).await;
Ok(user)
}
async fn find_user_by_password_reset_token(&self, token: &PasswordResetToken) -> Option<User> {
self.repo
.find_user_by_password_reset_token(token)
.await
.unwrap_or(None)
}
}