Refactor handlers and OpenAPI documentation for improved readability and consistency
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 6m49s
test / unit (pull_request) Successful in 16m24s
test / integration (pull_request) Failing after 17m7s

- Reorganized imports in health, notifications, social, thoughts, and users handlers for clarity.
- Updated function signatures in handlers to improve readability by aligning parameters.
- Enhanced JSON response formatting in notifications and thoughts handlers.
- Improved error handling in user-related functions.
- Refactored OpenAPI documentation to maintain consistent formatting and structure.
- Cleaned up unnecessary code and comments across various files.
- Ensured consistent use of `Arc` for shared state in AppState and WorkerHandlers.
This commit is contained in:
2026-05-14 16:28:57 +02:00
parent 004bfb427b
commit 10c4a66de5
47 changed files with 2406 additions and 723 deletions

View File

@@ -6,9 +6,16 @@ use domain::{
value_objects::{Email, UserId, Username},
};
pub struct RegisterInput { pub username: String, pub email: String, pub password: String }
pub struct RegisterInput {
pub username: String,
pub email: String,
pub password: String,
}
#[derive(Debug)]
pub struct RegisterOutput { pub user: User, pub token: String }
pub struct RegisterOutput {
pub user: User,
pub token: String,
}
pub async fn register(
users: &dyn UserRepository,
@@ -28,14 +35,27 @@ pub async fn register(
let hash = hasher.hash(&input.password).await?;
let user = User::new_local(UserId::new(), username, email, hash);
users.save(&user).await?;
events.publish(&DomainEvent::UserRegistered { user_id: user.id.clone() }).await?;
events
.publish(&DomainEvent::UserRegistered {
user_id: user.id.clone(),
})
.await?;
let token = auth.generate_token(&user.id)?;
Ok(RegisterOutput { user, token: token.token })
Ok(RegisterOutput {
user,
token: token.token,
})
}
pub struct LoginInput { pub email: String, pub password: String }
pub struct LoginInput {
pub email: String,
pub password: String,
}
#[derive(Debug)]
pub struct LoginOutput { pub user: User, pub token: String }
pub struct LoginOutput {
pub user: User,
pub token: String,
}
pub async fn login(
users: &dyn UserRepository,
@@ -44,12 +64,18 @@ pub async fn login(
input: LoginInput,
) -> Result<LoginOutput, DomainError> {
let email = Email::new(input.email)?;
let user = users.find_by_email(&email).await?.ok_or(DomainError::Unauthorized)?;
let user = users
.find_by_email(&email)
.await?
.ok_or(DomainError::Unauthorized)?;
if !hasher.verify(&input.password, &user.password_hash).await? {
return Err(DomainError::Unauthorized);
}
let token = auth.generate_token(&user.id)?;
Ok(LoginOutput { user, token: token.token })
Ok(LoginOutput {
user,
token: token.token,
})
}
#[cfg(test)]
@@ -65,29 +91,45 @@ mod tests {
};
struct FakeHasher;
#[async_trait] impl PasswordHasher for FakeHasher {
async fn hash(&self, plain: &str) -> Result<PasswordHash, DomainError> { Ok(PasswordHash(plain.to_string())) }
async fn verify(&self, plain: &str, hash: &PasswordHash) -> Result<bool, DomainError> { Ok(plain == hash.0) }
#[async_trait]
impl PasswordHasher for FakeHasher {
async fn hash(&self, plain: &str) -> Result<PasswordHash, DomainError> {
Ok(PasswordHash(plain.to_string()))
}
async fn verify(&self, plain: &str, hash: &PasswordHash) -> Result<bool, DomainError> {
Ok(plain == hash.0)
}
}
struct FakeAuth;
impl AuthService for FakeAuth {
fn generate_token(&self, uid: &UserId) -> Result<GeneratedToken, DomainError> {
Ok(GeneratedToken { token: uid.to_string(), user_id: uid.clone() })
Ok(GeneratedToken {
token: uid.to_string(),
user_id: uid.clone(),
})
}
fn validate_token(&self, token: &str) -> Result<UserId, DomainError> {
Ok(UserId::from_uuid(uuid::Uuid::parse_str(token).map_err(|_| DomainError::Unauthorized)?))
Ok(UserId::from_uuid(
uuid::Uuid::parse_str(token).map_err(|_| DomainError::Unauthorized)?,
))
}
}
fn input() -> RegisterInput {
RegisterInput { username: "alice".into(), email: "alice@ex.com".into(), password: "pw".into() }
RegisterInput {
username: "alice".into(),
email: "alice@ex.com".into(),
password: "pw".into(),
}
}
#[tokio::test]
async fn register_creates_user() {
let store = TestStore::default();
let out = register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap();
let out = register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap();
assert_eq!(out.user.username.as_str(), "alice");
assert!(!out.token.is_empty());
}
@@ -95,31 +137,61 @@ mod tests {
#[tokio::test]
async fn register_rejects_duplicate_username() {
let store = TestStore::default();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap();
let err = register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap_err();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap();
let err = register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap_err();
assert!(matches!(err, DomainError::Conflict(_)));
}
#[tokio::test]
async fn login_succeeds_with_correct_password() {
let store = TestStore::default();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap();
let out = login(&store, &FakeHasher, &FakeAuth, LoginInput { email: "alice@ex.com".into(), password: "pw".into() }).await.unwrap();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap();
let out = login(
&store,
&FakeHasher,
&FakeAuth,
LoginInput {
email: "alice@ex.com".into(),
password: "pw".into(),
},
)
.await
.unwrap();
assert!(!out.token.is_empty());
}
#[tokio::test]
async fn login_fails_wrong_password() {
let store = TestStore::default();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap();
let err = login(&store, &FakeHasher, &FakeAuth, LoginInput { email: "alice@ex.com".into(), password: "wrong".into() }).await.unwrap_err();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap();
let err = login(
&store,
&FakeHasher,
&FakeAuth,
LoginInput {
email: "alice@ex.com".into(),
password: "wrong".into(),
},
)
.await
.unwrap_err();
assert!(matches!(err, DomainError::Unauthorized));
}
#[tokio::test]
async fn register_publishes_user_registered_event() {
let store = TestStore::default();
register(&store, &FakeHasher, &FakeAuth, &store, input()).await.unwrap();
register(&store, &FakeHasher, &FakeAuth, &store, input())
.await
.unwrap();
let events = store.events.lock().unwrap();
assert_eq!(events.len(), 1);
assert!(matches!(events[0], DomainEvent::UserRegistered { .. }));
@@ -128,15 +200,39 @@ mod tests {
#[tokio::test]
async fn login_fails_for_nonexistent_user() {
let store = TestStore::default();
let err = login(&store, &FakeHasher, &FakeAuth, LoginInput { email: "ghost@ex.com".into(), password: "pass".into() }).await.unwrap_err();
let err = login(
&store,
&FakeHasher,
&FakeAuth,
LoginInput {
email: "ghost@ex.com".into(),
password: "pass".into(),
},
)
.await
.unwrap_err();
assert!(matches!(err, DomainError::Unauthorized));
}
#[tokio::test]
async fn register_rejects_duplicate_email() {
let store = TestStore::default();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input()).await.unwrap();
let err = register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, RegisterInput { username: "alice2".into(), email: "alice@ex.com".into(), password: "pass2".into() }).await.unwrap_err();
register(&store, &FakeHasher, &FakeAuth, &NoOpEventPublisher, input())
.await
.unwrap();
let err = register(
&store,
&FakeHasher,
&FakeAuth,
&NoOpEventPublisher,
RegisterInput {
username: "alice2".into(),
email: "alice@ex.com".into(),
password: "pass2".into(),
},
)
.await
.unwrap_err();
assert!(matches!(err, DomainError::Conflict(_)));
}
}