handlers: image/reindex/auth via use cases; auth::authenticate replaces ensure_active; check-handler-services guard
All checks were successful
CI / Check / Test (push) Successful in 1h10m22s
All checks were successful
CI / Check / Test (push) Successful in 1h10m22s
This commit is contained in:
11
crates/application/src/auth/authenticate.rs
Normal file
11
crates/application/src/auth/authenticate.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
use crate::auth::deps::AuthenticateDeps;
|
||||
|
||||
pub async fn execute(deps: &AuthenticateDeps, token: &str) -> Result<UserId, DomainError> {
|
||||
let user_id = deps.auth.validate_token(token).await?;
|
||||
match deps.user.find_by_id(&user_id).await? {
|
||||
Some(_) => Ok(user_id),
|
||||
None => Err(DomainError::Unauthorized("account is not active".into())),
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,11 @@ pub struct RegisterAndLoginDeps {
|
||||
pub config: AppSettings,
|
||||
}
|
||||
|
||||
pub struct AuthenticateDeps {
|
||||
pub auth: Arc<dyn AuthService>,
|
||||
pub user: Arc<dyn UserRepository>,
|
||||
}
|
||||
|
||||
pub struct LogoutDeps {
|
||||
pub refresh_session: Arc<dyn RefreshSessionRepository>,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod authenticate;
|
||||
pub mod commands;
|
||||
pub mod deps;
|
||||
pub mod login;
|
||||
|
||||
@@ -2,12 +2,15 @@ use std::sync::Arc;
|
||||
|
||||
use domain::ports::{EventPublisher, MediaServerParser, ObjectStorage, PersonEnrichmentClient};
|
||||
|
||||
use crate::auth::deps::{LoginDeps, LogoutDeps, RefreshDeps, RegisterAndLoginDeps, RegisterDeps};
|
||||
use crate::auth::deps::{
|
||||
AuthenticateDeps, LoginDeps, LogoutDeps, RefreshDeps, RegisterAndLoginDeps, RegisterDeps,
|
||||
};
|
||||
use crate::diary::deps::{
|
||||
DeleteReviewDeps, EditReviewDeps, ExportDiaryDeps, GetActivityFeedDeps, GetDiaryDeps,
|
||||
GetMovieSocialPageDeps, GetReviewHistoryDeps, GetUserFeedDeps,
|
||||
};
|
||||
use crate::goals::deps::{GoalCommandDeps, GoalQueryDeps};
|
||||
use crate::images::deps::GetImageDeps;
|
||||
use crate::import::deps::{
|
||||
ApplyMappingDeps, ApplyProfileAndMapDeps, ApplyProfileDeps, CreateSessionDeps,
|
||||
DeleteImportProfileDeps, ExecuteImportDeps, GetMappingStageDeps, GetPreviewStageDeps,
|
||||
@@ -23,10 +26,10 @@ use crate::movies::deps::{
|
||||
};
|
||||
use crate::movies::merge_duplicates::MergeDuplicatesDeps;
|
||||
use crate::person::deps::{EnrichPersonDeps, GetPersonDeps};
|
||||
use crate::search::deps::SearchDeps;
|
||||
use crate::search::deps::{RequestReindexDeps, SearchDeps};
|
||||
use crate::social::deps::{SocialCommandDeps, SocialQueryDeps};
|
||||
use crate::users::deps::{
|
||||
AuthorizeAdminDeps, EnsureActiveDeps, GetCurrentProfileDeps, GetFederatedProfileDeps,
|
||||
AuthorizeAdminDeps, GetCurrentProfileDeps, GetFederatedProfileDeps,
|
||||
GetFederatedProfileStatsDeps, GetLocalProfileDeps, GetPageViewerDeps, GetProfileSettingsDeps,
|
||||
GetSettingsDeps, GetUsersListDeps, RequestAccountDeletionDeps, ResolveUsernameDeps,
|
||||
UpdateProfileDeps, UpdateProfileFieldsDeps, UpdateSettingsDeps,
|
||||
@@ -46,6 +49,7 @@ pub struct AuthGroup {
|
||||
pub refresh: RefreshDeps,
|
||||
pub register_and_login: RegisterAndLoginDeps,
|
||||
pub logout: LogoutDeps,
|
||||
pub authenticate: AuthenticateDeps,
|
||||
}
|
||||
|
||||
pub struct DiaryGroup {
|
||||
@@ -64,6 +68,10 @@ pub struct GoalsGroup {
|
||||
pub query: GoalQueryDeps,
|
||||
}
|
||||
|
||||
pub struct ImagesGroup {
|
||||
pub get_image: GetImageDeps,
|
||||
}
|
||||
|
||||
pub struct ImportGroup {
|
||||
pub create_session: CreateSessionDeps,
|
||||
pub apply_mapping: ApplyMappingDeps,
|
||||
@@ -109,6 +117,7 @@ pub struct PersonGroup {
|
||||
|
||||
pub struct SearchGroup {
|
||||
pub execute: SearchDeps,
|
||||
pub request_reindex: RequestReindexDeps,
|
||||
}
|
||||
|
||||
pub struct SocialGroup {
|
||||
@@ -125,7 +134,6 @@ pub struct UsersGroup {
|
||||
pub get_users_list: GetUsersListDeps,
|
||||
pub update_profile: UpdateProfileDeps,
|
||||
pub request_account_deletion: RequestAccountDeletionDeps,
|
||||
pub ensure_active: EnsureActiveDeps,
|
||||
pub get_current_profile: GetCurrentProfileDeps,
|
||||
pub update_profile_fields: UpdateProfileFieldsDeps,
|
||||
pub get_settings: GetSettingsDeps,
|
||||
@@ -163,6 +171,7 @@ pub struct Deps {
|
||||
pub auth: AuthGroup,
|
||||
pub diary: DiaryGroup,
|
||||
pub goals: GoalsGroup,
|
||||
pub images: ImagesGroup,
|
||||
pub import: ImportGroup,
|
||||
pub insights: InsightsGroup,
|
||||
pub integrations: IntegrationsGroup,
|
||||
|
||||
7
crates/application/src/images/deps.rs
Normal file
7
crates/application/src/images/deps.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ports::ObjectStorage;
|
||||
|
||||
pub struct GetImageDeps {
|
||||
pub object_storage: Arc<dyn ObjectStorage>,
|
||||
}
|
||||
7
crates/application/src/images/get_image.rs
Normal file
7
crates/application/src/images/get_image.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use domain::errors::DomainError;
|
||||
|
||||
use crate::images::deps::GetImageDeps;
|
||||
|
||||
pub async fn execute(deps: &GetImageDeps, key: &str) -> Result<Vec<u8>, DomainError> {
|
||||
deps.object_storage.get(key).await
|
||||
}
|
||||
2
crates/application/src/images/mod.rs
Normal file
2
crates/application/src/images/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod deps;
|
||||
pub mod get_image;
|
||||
@@ -8,6 +8,7 @@ pub mod worker;
|
||||
pub mod auth;
|
||||
pub mod diary;
|
||||
pub mod goals;
|
||||
pub mod images;
|
||||
pub mod import;
|
||||
pub mod insights;
|
||||
pub mod integrations;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ports::SearchPort;
|
||||
use domain::ports::{EventPublisher, SearchPort};
|
||||
|
||||
pub struct SearchDeps {
|
||||
pub search_port: Arc<dyn SearchPort>,
|
||||
}
|
||||
|
||||
pub struct RequestReindexDeps {
|
||||
pub event_publisher: Arc<dyn EventPublisher>,
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod deps;
|
||||
pub mod execute;
|
||||
pub mod request_reindex;
|
||||
|
||||
9
crates/application/src/search/request_reindex.rs
Normal file
9
crates/application/src/search/request_reindex.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent};
|
||||
|
||||
use crate::search::deps::RequestReindexDeps;
|
||||
|
||||
pub async fn execute(deps: &RequestReindexDeps) -> Result<(), DomainError> {
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::SearchReindexRequested)
|
||||
.await
|
||||
}
|
||||
@@ -52,10 +52,6 @@ pub struct RequestAccountDeletionDeps {
|
||||
pub password_hasher: Arc<dyn PasswordHasher>,
|
||||
}
|
||||
|
||||
pub struct EnsureActiveDeps {
|
||||
pub user: Arc<dyn UserRepository>,
|
||||
}
|
||||
|
||||
pub struct GetCurrentProfileDeps {
|
||||
pub user: Arc<dyn UserRepository>,
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
use crate::users::deps::EnsureActiveDeps;
|
||||
|
||||
pub async fn execute(deps: &EnsureActiveDeps, user: &UserId) -> Result<(), DomainError> {
|
||||
match deps.user.find_by_id(user).await? {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(DomainError::Unauthorized("account is not active".into())),
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pub mod authorize_admin;
|
||||
pub mod commands;
|
||||
pub mod deps;
|
||||
mod diary_filter;
|
||||
pub mod ensure_active;
|
||||
pub mod get_current_profile;
|
||||
pub mod get_federated_profile;
|
||||
pub mod get_federated_profile_stats;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
mod support;
|
||||
|
||||
#[path = "auth/authenticate_test.rs"]
|
||||
mod authenticate_test;
|
||||
#[path = "auth/login_test.rs"]
|
||||
mod login_test;
|
||||
#[path = "auth/logout_test.rs"]
|
||||
|
||||
@@ -7,15 +7,16 @@ use domain::{
|
||||
value_objects::{Email, UserId},
|
||||
};
|
||||
|
||||
use application::{
|
||||
auth::{deps::RegisterDeps, register},
|
||||
users::{deps::EnsureActiveDeps, ensure_active},
|
||||
use application::auth::{
|
||||
authenticate,
|
||||
deps::{AuthenticateDeps, RegisterDeps},
|
||||
register,
|
||||
};
|
||||
|
||||
use crate::support::{TestContextBuilder, register_command};
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_live_user_is_active() {
|
||||
async fn a_live_users_token_authenticates() {
|
||||
let store = InMemoryStore::new();
|
||||
let b = TestContextBuilder::new().with_users(Arc::clone(&store) as _);
|
||||
let reg = RegisterDeps {
|
||||
@@ -33,15 +34,20 @@ async fn a_live_user_is_active() {
|
||||
.unwrap()
|
||||
.id()
|
||||
.clone();
|
||||
let deps = EnsureActiveDeps {
|
||||
let deps = AuthenticateDeps {
|
||||
auth: b.auth_service.clone(),
|
||||
user: b.user_repo.clone(),
|
||||
};
|
||||
|
||||
ensure_active::execute(&deps, &id).await.unwrap();
|
||||
let authenticated = authenticate::execute(&deps, &id.value().to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(authenticated, id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_deleted_user_is_rejected() {
|
||||
async fn a_deleted_users_token_is_rejected() {
|
||||
let store = InMemoryStore::new();
|
||||
let b = TestContextBuilder::new().with_users(Arc::clone(&store) as _);
|
||||
let reg = RegisterDeps {
|
||||
@@ -60,23 +66,42 @@ async fn a_deleted_user_is_rejected() {
|
||||
.id()
|
||||
.clone();
|
||||
store.mark_deleted(&id, &[]).await.unwrap();
|
||||
let deps = EnsureActiveDeps {
|
||||
let deps = AuthenticateDeps {
|
||||
auth: b.auth_service.clone(),
|
||||
user: b.user_repo.clone(),
|
||||
};
|
||||
|
||||
let err = ensure_active::execute(&deps, &id).await.unwrap_err();
|
||||
|
||||
assert!(matches!(err, DomainError::Unauthorized(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_unknown_user_is_rejected() {
|
||||
let b = TestContextBuilder::new();
|
||||
let deps = EnsureActiveDeps {
|
||||
user: b.user_repo.clone(),
|
||||
};
|
||||
|
||||
let err = ensure_active::execute(&deps, &UserId::generate())
|
||||
let err = authenticate::execute(&deps, &id.value().to_string())
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, DomainError::Unauthorized(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_unknown_users_token_is_rejected() {
|
||||
let b = TestContextBuilder::new();
|
||||
let deps = AuthenticateDeps {
|
||||
auth: b.auth_service.clone(),
|
||||
user: b.user_repo.clone(),
|
||||
};
|
||||
|
||||
let err = authenticate::execute(&deps, &UserId::generate().value().to_string())
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, DomainError::Unauthorized(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_invalid_token_is_rejected() {
|
||||
let b = TestContextBuilder::new();
|
||||
let deps = AuthenticateDeps {
|
||||
auth: b.auth_service.clone(),
|
||||
user: b.user_repo.clone(),
|
||||
};
|
||||
|
||||
let err = authenticate::execute(&deps, "not-a-token")
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
2
crates/application/tests/images.rs
Normal file
2
crates/application/tests/images.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
#[path = "images/get_image_test.rs"]
|
||||
mod get_image_test;
|
||||
76
crates/application/tests/images/get_image_test.rs
Normal file
76
crates/application/tests/images/get_image_test.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, ports::ObjectStorage};
|
||||
|
||||
use application::images::{deps::GetImageDeps, get_image};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Storage {
|
||||
objects: Mutex<HashMap<String, Vec<u8>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ObjectStorage for Storage {
|
||||
async fn store(&self, key: &str, image_bytes: &[u8]) -> Result<String, DomainError> {
|
||||
self.objects
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(key.to_string(), image_bytes.to_vec());
|
||||
Ok(key.to_string())
|
||||
}
|
||||
|
||||
async fn get(&self, key: &str) -> Result<Vec<u8>, DomainError> {
|
||||
self.objects
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(key)
|
||||
.cloned()
|
||||
.ok_or_else(|| DomainError::NotFound(key.to_string()))
|
||||
}
|
||||
|
||||
async fn get_stream(
|
||||
&self,
|
||||
_key: &str,
|
||||
) -> Result<futures::stream::BoxStream<'static, Result<bytes::Bytes, DomainError>>, DomainError>
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn delete(&self, _key: &str) -> Result<(), DomainError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn exists(&self, key: &str) -> Result<bool, DomainError> {
|
||||
Ok(self.objects.lock().unwrap().contains_key(key))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_the_stored_bytes() {
|
||||
let storage = Arc::new(Storage::default());
|
||||
storage.store("posters/1.webp", b"image").await.unwrap();
|
||||
let deps = GetImageDeps {
|
||||
object_storage: storage,
|
||||
};
|
||||
|
||||
let bytes = get_image::execute(&deps, "posters/1.webp").await.unwrap();
|
||||
|
||||
assert_eq!(bytes, b"image");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_missing_key_is_not_found() {
|
||||
let deps = GetImageDeps {
|
||||
object_storage: Arc::new(Storage::default()),
|
||||
};
|
||||
|
||||
let err = get_image::execute(&deps, "posters/missing.webp")
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, DomainError::NotFound(_)));
|
||||
}
|
||||
@@ -2,3 +2,6 @@ mod support;
|
||||
|
||||
#[path = "search/execute_test.rs"]
|
||||
mod execute_test;
|
||||
|
||||
#[path = "search/request_reindex_test.rs"]
|
||||
mod request_reindex_test;
|
||||
|
||||
33
crates/application/tests/search/request_reindex_test.rs
Normal file
33
crates/application/tests/search/request_reindex_test.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
testing::{NoopEventPublisher, RefusingEventPublisher},
|
||||
};
|
||||
|
||||
use application::search::{deps::RequestReindexDeps, request_reindex};
|
||||
|
||||
#[tokio::test]
|
||||
async fn publishes_a_reindex_request() {
|
||||
let publisher = NoopEventPublisher::new();
|
||||
let deps = RequestReindexDeps {
|
||||
event_publisher: publisher.clone(),
|
||||
};
|
||||
|
||||
request_reindex::execute(&deps).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
publisher.published().as_slice(),
|
||||
[DomainEvent::SearchReindexRequested]
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_publish_failure_is_returned() {
|
||||
let deps = RequestReindexDeps {
|
||||
event_publisher: std::sync::Arc::new(RefusingEventPublisher),
|
||||
};
|
||||
|
||||
let err = request_reindex::execute(&deps).await.unwrap_err();
|
||||
|
||||
assert!(matches!(err, DomainError::InfrastructureError(_)));
|
||||
}
|
||||
@@ -2,8 +2,6 @@ mod support;
|
||||
|
||||
#[path = "users/authorize_admin_test.rs"]
|
||||
mod authorize_admin_test;
|
||||
#[path = "users/ensure_active_test.rs"]
|
||||
mod ensure_active_test;
|
||||
#[path = "users/get_current_profile_test.rs"]
|
||||
mod get_current_profile_test;
|
||||
#[path = "users/get_federated_profile_stats_test.rs"]
|
||||
|
||||
Reference in New Issue
Block a user