Skip to content

Commit c22344b

Browse files
authored
Merge pull request #156 from rambit-systems/push-nmkoymquqnto
More isolation and modularization of domain services
2 parents dcc5fc3 + 080f047 commit c22344b

File tree

26 files changed

+636
-409
lines changed

26 files changed

+636
-409
lines changed

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/auth-domain/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ publish = false
99
[dependencies]
1010
models = { path = "../models" }
1111

12-
db.workspace = true
12+
meta-domain = { path = "../meta-domain" }
13+
mutate-domain = { path = "../mutate-domain" }
1314

1415
miette.workspace = true
1516
thiserror.workspace = true

crates/auth-domain/src/errors.rs

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,17 @@
1-
use db::{FetchModelByIndexError, FetchModelError, PatchModelError};
2-
use models::{Org, User, dvf::EmailAddress, model::RecordId};
1+
use models::dvf::EmailAddress;
32

43
/// An error that occurs during user creation.
54
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
65
pub enum CreateUserError {
76
/// Indicates that the user's email address is already in use.
87
#[error("The email address is already in use: \"{0}\"")]
98
EmailAlreadyUsed(EmailAddress),
10-
/// Indicates than an error occurred while hashing the password.
11-
#[error("Failed to hash password")]
12-
PasswordHashing(miette::Report),
13-
/// Indicates that an error occurred while creating the user.
9+
/// Indicates that an internal error occurred.
1410
#[error("Failed to create the user")]
15-
CreateError(miette::Report),
16-
/// Indicates that an error occurred while fetching users by index.
17-
#[error("Failed to fetch users by index")]
18-
FetchByIndexError(#[from] FetchModelByIndexError),
11+
InternalError(miette::Report),
1912
}
2013

2114
/// An error that occurs during user authentication.
2215
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
23-
pub enum AuthenticationError {
24-
/// Indicates that an error occurred while fetching users.
25-
#[error("Failed to fetch user")]
26-
FetchError(#[from] FetchModelError),
27-
/// Indicates that an error occurred while fetching users by index.
28-
#[error("Failed to fetch user by index")]
29-
FetchByIndexError(#[from] FetchModelByIndexError),
30-
/// Indicates than an error occurred while hashing the password.
31-
#[error("Failed to hash password")]
32-
PasswordHashing(miette::Report),
33-
}
34-
35-
/// An error that occurs while updating a user's active org.
36-
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
37-
pub enum UpdateActiveOrgError {
38-
/// Indicates that an error occurred while fetching the user.
39-
#[error("Failed to fetch user")]
40-
FetchError(#[from] FetchModelError),
41-
/// Indicates that the user does not exist.
42-
#[error("Failed to find user: {0}")]
43-
MissingUser(RecordId<User>),
44-
/// Indicates that the org supplied could not be switched to.
45-
#[error("Failed to switch to org: {0}")]
46-
InvalidOrg(RecordId<Org>),
47-
/// Indicates that an error occurred while patching the user record.
48-
#[error("Failed to patch user")]
49-
PatchError(#[from] PatchModelError),
50-
}
16+
#[error("Internal error: {0}")]
17+
pub struct AuthenticationError(pub miette::Report);

crates/auth-domain/src/lib.rs

Lines changed: 49 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ mod tests;
77

88
use axum_login::AuthUser as AxumLoginAuthUser;
99
pub use axum_login::AuthnBackend;
10-
use db::{Database, FetchModelByIndexError, FetchModelError, kv::LaxSlug};
11-
use miette::{IntoDiagnostic, miette};
10+
use miette::{Context, IntoDiagnostic, miette};
1211
use models::{
1312
AuthUser, Org, OrgIdent, User, UserAuthCredentials,
14-
UserSubmittedAuthCredentials, UserUniqueIndexSelector,
15-
dvf::{EitherSlug, EmailAddress, HumanName},
13+
UserSubmittedAuthCredentials,
14+
dvf::{EmailAddress, HumanName},
1615
model::RecordId,
1716
};
17+
pub use mutate_domain::UpdateActiveOrgError;
1818

1919
pub use self::errors::*;
2020

@@ -24,70 +24,29 @@ pub type AuthSession = axum_login::AuthSession<AuthDomainService>;
2424
/// A dynamic [`AuthDomainService`] trait object.
2525
#[derive(Clone, Debug)]
2626
pub struct AuthDomainService {
27-
org_repo: Database<Org>,
28-
user_repo: Database<User>,
27+
meta: meta_domain::MetaService,
28+
mutate: mutate_domain::MutationService,
2929
}
3030

3131
impl AuthDomainService {
3232
/// Creates a new [`AuthDomainService`].
3333
#[must_use]
34-
pub fn new(org_repo: Database<Org>, user_repo: Database<User>) -> Self {
35-
Self {
36-
org_repo,
37-
user_repo,
38-
}
34+
pub fn new(
35+
meta: meta_domain::MetaService,
36+
mutate: mutate_domain::MutationService,
37+
) -> Self {
38+
Self { meta, mutate }
3939
}
4040
}
4141

4242
impl AuthDomainService {
43-
/// Fetch a [`User`] by ID.
44-
async fn fetch_user_by_id(
45-
&self,
46-
id: RecordId<User>,
47-
) -> Result<Option<User>, FetchModelError> {
48-
self.user_repo.fetch_model_by_id(id).await
49-
}
50-
51-
/// Fetch a [`User`] by [`EmailAddress`](EmailAddress).
52-
async fn fetch_user_by_email(
53-
&self,
54-
email: EmailAddress,
55-
) -> Result<Option<User>, FetchModelByIndexError> {
56-
self
57-
.user_repo
58-
.fetch_model_by_unique_index(
59-
UserUniqueIndexSelector::Email,
60-
EitherSlug::Lax(LaxSlug::new(email.as_ref())),
61-
)
62-
.await
63-
}
64-
65-
/// Switch a [`User`]'s active org.
43+
/// Switches the active org of a [`User`].
6644
pub async fn switch_active_org(
6745
&self,
6846
user: RecordId<User>,
6947
new_active_org: RecordId<Org>,
70-
) -> Result<RecordId<Org>, errors::UpdateActiveOrgError> {
71-
let user = self
72-
.user_repo
73-
.fetch_model_by_id(user)
74-
.await?
75-
.ok_or(errors::UpdateActiveOrgError::MissingUser(user))?;
76-
77-
let new_index = user
78-
.iter_orgs()
79-
.position(|o| o == new_active_org)
80-
.ok_or(errors::UpdateActiveOrgError::InvalidOrg(new_active_org))?;
81-
82-
self
83-
.user_repo
84-
.patch_model(user.id, User {
85-
active_org_index: new_index as _,
86-
..user
87-
})
88-
.await?;
89-
90-
Ok(new_active_org)
48+
) -> Result<RecordId<Org>, UpdateActiveOrgError> {
49+
self.mutate.switch_active_org(user, new_active_org).await
9150
}
9251

9352
/// Sign up a [`User`].
@@ -99,7 +58,15 @@ impl AuthDomainService {
9958
) -> Result<User, errors::CreateUserError> {
10059
use argon2::PasswordHasher;
10160

102-
if self.fetch_user_by_email(email.clone()).await?.is_some() {
61+
if self
62+
.meta
63+
.fetch_user_by_email(email.clone())
64+
.await
65+
.into_diagnostic()
66+
.context("failed to check for conflicting user by email")
67+
.map_err(CreateUserError::InternalError)?
68+
.is_some()
69+
{
10370
return Err(errors::CreateUserError::EmailAlreadyUsed(email));
10471
}
10572

@@ -113,8 +80,8 @@ impl AuthDomainService {
11380
argon
11481
.hash_password(password.as_bytes(), &salt)
11582
.map_err(|e| {
116-
errors::CreateUserError::PasswordHashing(miette!(
117-
"failed to hash password: {e}"
83+
CreateUserError::InternalError(miette!(
84+
"failed to parse password hash: {e}"
11885
))
11986
})?
12087
.to_string(),
@@ -143,29 +110,40 @@ impl AuthDomainService {
143110
};
144111

145112
self
146-
.org_repo
147-
.create_model(org)
113+
.mutate
114+
.create_org(org)
148115
.await
149116
.into_diagnostic()
150-
.map_err(errors::CreateUserError::CreateError)?;
117+
.context("failed to create personal org for user")
118+
.map_err(errors::CreateUserError::InternalError)?;
151119

152120
self
153-
.user_repo
154-
.create_model(user)
121+
.mutate
122+
.create_user(user.clone())
155123
.await
156124
.into_diagnostic()
157-
.map_err(errors::CreateUserError::CreateError)
125+
.context("failed to create user")
126+
.map_err(errors::CreateUserError::InternalError)?;
127+
128+
Ok(user)
158129
}
159130

160131
/// Authenticate a [`User`].
161132
pub async fn user_authenticate(
162133
&self,
163134
email: EmailAddress,
164135
creds: UserSubmittedAuthCredentials,
165-
) -> Result<Option<User>, errors::AuthenticationError> {
136+
) -> Result<Option<User>, AuthenticationError> {
166137
use argon2::PasswordVerifier;
167138

168-
let Some(user) = self.fetch_user_by_email(email).await? else {
139+
let Some(user) = self
140+
.meta
141+
.fetch_user_by_email(email)
142+
.await
143+
.into_diagnostic()
144+
.context("failed to fetch user by email")
145+
.map_err(AuthenticationError)?
146+
else {
169147
return Ok(None);
170148
};
171149

@@ -176,9 +154,7 @@ impl AuthDomainService {
176154
) => {
177155
let password_hash = argon2::PasswordHash::new(&password_hash.0)
178156
.map_err(|e| {
179-
errors::AuthenticationError::PasswordHashing(miette!(
180-
"failed to parse password hash: {e}"
181-
))
157+
AuthenticationError(miette!("failed to parse password hash: {e}"))
182158
})?;
183159

184160
let argon = argon2::Argon2::default();
@@ -189,7 +165,7 @@ impl AuthDomainService {
189165
Err(e) => Err(e),
190166
})
191167
.map_err(|e| {
192-
errors::AuthenticationError::PasswordHashing(miette!(
168+
AuthenticationError(miette!(
193169
"failed to verify password against hash: {e}"
194170
))
195171
})?;
@@ -220,9 +196,11 @@ impl AuthnBackend for AuthDomainService {
220196
id: &<Self::User as AxumLoginAuthUser>::Id,
221197
) -> Result<Option<Self::User>, Self::Error> {
222198
self
199+
.meta
223200
.fetch_user_by_id(*id)
224201
.await
202+
.into_diagnostic()
225203
.map(|u| u.map(Into::into))
226-
.map_err(Into::into)
204+
.map_err(AuthenticationError)
227205
}
228206
}

crates/auth-domain/src/tests.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
use meta_domain::MetaService;
12
use models::dvf::{EmailAddress, HumanName};
3+
use mutate_domain::MutationService;
24

35
use super::*;
46

57
#[tokio::test]
68
async fn test_user_signup() {
7-
let org_repo = Database::new_mock();
8-
let user_repo = Database::new_mock();
9-
let service = AuthDomainService::new(org_repo, user_repo);
9+
let service = AuthDomainService::new(
10+
MetaService::new_mock(),
11+
MutationService::new_mock(),
12+
);
1013

1114
let name = HumanName::try_new("Test User 1").unwrap();
1215
let email = EmailAddress::try_new("test@example.com").unwrap();
@@ -30,9 +33,10 @@ async fn test_user_signup() {
3033

3134
#[tokio::test]
3235
async fn test_user_authenticate() {
33-
let org_repo = Database::new_mock();
34-
let user_repo = Database::new_mock();
35-
let service = AuthDomainService::new(org_repo, user_repo);
36+
let service = AuthDomainService::new(
37+
MetaService::new_mock(),
38+
MutationService::new_mock(),
39+
);
3640

3741
let name = HumanName::try_new("Test User 1").unwrap();
3842
let email = EmailAddress::try_new("test@example.com").unwrap();

crates/domain/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ publish = false
99
[dependencies]
1010
meta-domain = { path = "../meta-domain" }
1111
models = { path = "../models" }
12+
mutate-domain = { path = "../mutate-domain" }
1213
owl = { path = "../owl" }
1314

1415
belt.workspace = true

0 commit comments

Comments
 (0)