This commit is contained in:
2026-07-10 16:03:36 +02:00
parent 44d7df33a2
commit 2484f1e603
26 changed files with 217 additions and 224 deletions

View File

@@ -1,5 +1,4 @@
pub mod composite_handler; pub mod composite_handler;
pub mod social_adapter;
pub mod event_handler; pub mod event_handler;
pub mod federation_event_bridge; pub mod federation_event_bridge;
pub mod goal_handler; pub mod goal_handler;
@@ -7,6 +6,7 @@ pub mod objects;
pub mod port; pub mod port;
pub mod remote_review_repository; pub mod remote_review_repository;
pub mod review_handler; pub mod review_handler;
pub mod social_adapter;
pub(crate) mod urls; pub(crate) mod urls;
pub mod user_adapter; pub mod user_adapter;
pub mod watchlist_handler; pub mod watchlist_handler;

View File

@@ -87,11 +87,7 @@ fn ap_err(e: anyhow::Error) -> DomainError {
#[async_trait] #[async_trait]
impl SocialCommand for CompositeSocialAdapter { impl SocialCommand for CompositeSocialAdapter {
async fn follow( async fn follow(&self, follower: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
follower: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
if let SocialIdentity::Local(target_id) = target if let SocialIdentity::Local(target_id) = target
&& follower == target_id && follower == target_id
{ {
@@ -154,11 +150,7 @@ impl SocialCommand for CompositeSocialAdapter {
.map_err(ap_err) .map_err(ap_err)
} }
async fn block( async fn block(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
let actor_url = self.actor_url_from_identity(target); let actor_url = self.actor_url_from_identity(target);
self.ap_service self.ap_service
.block_actor(blocker.value(), &actor_url) .block_actor(blocker.value(), &actor_url)
@@ -166,11 +158,7 @@ impl SocialCommand for CompositeSocialAdapter {
.map_err(ap_err) .map_err(ap_err)
} }
async fn unblock( async fn unblock(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
let actor_url = self.actor_url_from_identity(target); let actor_url = self.actor_url_from_identity(target);
self.ap_service self.ap_service
.unblock_actor(blocker.value(), &actor_url) .unblock_actor(blocker.value(), &actor_url)
@@ -181,10 +169,7 @@ impl SocialCommand for CompositeSocialAdapter {
#[async_trait] #[async_trait]
impl SocialQuery for CompositeSocialAdapter { impl SocialQuery for CompositeSocialAdapter {
async fn get_following( async fn get_following(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let actors = self let actors = self
.ap_service .ap_service
.get_following(user.value()) .get_following(user.value())
@@ -196,10 +181,7 @@ impl SocialQuery for CompositeSocialAdapter {
.collect()) .collect())
} }
async fn get_followers( async fn get_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let actors = self let actors = self
.ap_service .ap_service
.get_accepted_followers(user.value()) .get_accepted_followers(user.value())
@@ -211,10 +193,7 @@ impl SocialQuery for CompositeSocialAdapter {
.collect()) .collect())
} }
async fn get_pending_followers( async fn get_pending_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let actors = self let actors = self
.ap_service .ap_service
.get_pending_followers(user.value()) .get_pending_followers(user.value())
@@ -240,10 +219,7 @@ impl SocialQuery for CompositeSocialAdapter {
.map_err(ap_err) .map_err(ap_err)
} }
async fn get_blocked( async fn get_blocked(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let actors = self let actors = self
.ap_service .ap_service
.get_blocked_actors(user.value()) .get_blocked_actors(user.value())

View File

@@ -1,9 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use domain::{ use domain::{errors::DomainError, models::RemoteActorInfo, ports::FederationAdminQuery};
errors::DomainError,
models::RemoteActorInfo,
ports::FederationAdminQuery,
};
use super::PostgresFederationRepository; use super::PostgresFederationRepository;

View File

@@ -1,9 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use domain::{ use domain::{errors::DomainError, models::RemoteActorInfo, ports::FederationAdminQuery};
errors::DomainError,
models::RemoteActorInfo,
ports::FederationAdminQuery,
};
use super::SqliteFederationRepository; use super::SqliteFederationRepository;

View File

@@ -1,4 +1,7 @@
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}}; use domain::{
errors::DomainError,
value_objects::{SocialActor, UserId},
};
use super::{deps::SocialQueryDeps, queries::GetBlockedQuery}; use super::{deps::SocialQueryDeps, queries::GetBlockedQuery};

View File

@@ -1,4 +1,7 @@
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}}; use domain::{
errors::DomainError,
value_objects::{SocialActor, UserId},
};
use super::{deps::SocialQueryDeps, queries::GetFollowersQuery}; use super::{deps::SocialQueryDeps, queries::GetFollowersQuery};

View File

@@ -1,4 +1,7 @@
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}}; use domain::{
errors::DomainError,
value_objects::{SocialActor, UserId},
};
use super::{deps::SocialQueryDeps, queries::GetFollowingQuery}; use super::{deps::SocialQueryDeps, queries::GetFollowingQuery};

View File

@@ -1,4 +1,7 @@
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}}; use domain::{
errors::DomainError,
value_objects::{SocialActor, UserId},
};
use super::{deps::SocialQueryDeps, queries::GetPendingFollowersQuery}; use super::{deps::SocialQueryDeps, queries::GetPendingFollowersQuery};

View File

@@ -14,7 +14,11 @@ use crate::social::{
follow, follow,
}; };
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -53,7 +57,9 @@ async fn accept_follow_emits_follow_accepted_event() {
.unwrap(); .unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::FollowAccepted { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::FollowAccepted { .. }))
);
} }

View File

@@ -9,7 +9,11 @@ use uuid::Uuid;
use crate::social::{block, commands::BlockCommand, deps::SocialCommandDeps}; use crate::social::{block, commands::BlockCommand, deps::SocialCommandDeps};
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -35,7 +39,9 @@ async fn block_emits_actor_blocked_event() {
.unwrap(); .unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::ActorBlocked { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::ActorBlocked { .. }))
);
} }

View File

@@ -9,7 +9,11 @@ use uuid::Uuid;
use crate::social::{commands::FollowCommand, deps::SocialCommandDeps, follow}; use crate::social::{commands::FollowCommand, deps::SocialCommandDeps, follow};
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -35,9 +39,11 @@ async fn follow_emits_follow_requested_event() {
.unwrap(); .unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::FollowRequested { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::FollowRequested { .. }))
);
} }
#[tokio::test] #[tokio::test]

View File

@@ -50,13 +50,8 @@ async fn returns_accepted_followers() {
.await .await
.unwrap(); .unwrap();
let followers = get_followers::execute( let followers = get_followers::execute(&query_deps, GetFollowersQuery { user_id: owner_id })
&query_deps, .await
GetFollowersQuery { .unwrap();
user_id: owner_id,
},
)
.await
.unwrap();
assert_eq!(followers.len(), 1); assert_eq!(followers.len(), 1);
} }

View File

@@ -39,13 +39,8 @@ async fn returns_only_pending_followers() {
.await .await
.unwrap(); .unwrap();
let pending = get_pending::execute( let pending = get_pending::execute(&query_deps, GetPendingFollowersQuery { user_id: owner_id })
&query_deps, .await
GetPendingFollowersQuery { .unwrap();
user_id: owner_id,
},
)
.await
.unwrap();
assert_eq!(pending.len(), 1); assert_eq!(pending.len(), 1);
} }

View File

@@ -12,7 +12,11 @@ use crate::social::{
follow, reject, follow, reject,
}; };
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {

View File

@@ -14,7 +14,11 @@ use crate::social::{
follow, remove_follower, follow, remove_follower,
}; };
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -62,7 +66,9 @@ async fn remove_follower_emits_follower_removed_event() {
.unwrap(); .unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::FollowerRemoved { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::FollowerRemoved { .. }))
);
} }

View File

@@ -8,12 +8,17 @@ use domain::{
use uuid::Uuid; use uuid::Uuid;
use crate::social::{ use crate::social::{
block,
commands::{BlockCommand, UnblockCommand}, commands::{BlockCommand, UnblockCommand},
deps::SocialCommandDeps, deps::SocialCommandDeps,
block, unblock, unblock,
}; };
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -40,18 +45,14 @@ async fn unblock_emits_actor_unblocked_event() {
.await .await
.unwrap(); .unwrap();
unblock::execute( unblock::execute(&deps, UnblockCommand { blocker_id, target })
&deps, .await
UnblockCommand { .unwrap();
blocker_id,
target,
},
)
.await
.unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::ActorUnblocked { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::ActorUnblocked { .. }))
);
} }

View File

@@ -13,7 +13,11 @@ use crate::social::{
follow, unfollow, follow, unfollow,
}; };
fn make_deps() -> (Arc<InMemorySocialRepository>, Arc<NoopEventPublisher>, SocialCommandDeps) { fn make_deps() -> (
Arc<InMemorySocialRepository>,
Arc<NoopEventPublisher>,
SocialCommandDeps,
) {
let social = InMemorySocialRepository::new(); let social = InMemorySocialRepository::new();
let events = NoopEventPublisher::new(); let events = NoopEventPublisher::new();
let deps = SocialCommandDeps { let deps = SocialCommandDeps {
@@ -51,7 +55,9 @@ async fn unfollow_emits_unfollowed_event() {
.unwrap(); .unwrap();
let published = events.published(); let published = events.published();
assert!(published assert!(
.iter() published
.any(|e| matches!(e, DomainEvent::Unfollowed { .. }))); .iter()
.any(|e| matches!(e, DomainEvent::Unfollowed { .. }))
);
} }

View File

@@ -4,9 +4,7 @@ use super::{commands::UnfollowCommand, deps::SocialCommandDeps};
pub async fn execute(deps: &SocialCommandDeps, cmd: UnfollowCommand) -> Result<(), DomainError> { pub async fn execute(deps: &SocialCommandDeps, cmd: UnfollowCommand) -> Result<(), DomainError> {
let follower = UserId::from_uuid(cmd.follower_id); let follower = UserId::from_uuid(cmd.follower_id);
deps.social_command deps.social_command.unfollow(&follower, &cmd.target).await?;
.unfollow(&follower, &cmd.target)
.await?;
deps.event_publisher deps.event_publisher
.publish(&DomainEvent::Unfollowed { .publish(&DomainEvent::Unfollowed {
follower, follower,

View File

@@ -4,8 +4,8 @@ use chrono::NaiveDateTime;
use crate::{ use crate::{
errors::DomainError, errors::DomainError,
models::{ models::{
DiaryEntry, FederationFlags, RemoteActorInfo, RemoteGoalEntry, DiaryEntry, FederationFlags, RemoteActorInfo, RemoteGoalEntry, RemoteWatchlistEntry,
RemoteWatchlistEntry, WatchlistWithMovie, WatchlistWithMovie,
}, },
value_objects::{MovieId, SocialActor, SocialIdentity, UserId}, value_objects::{MovieId, SocialActor, SocialIdentity, UserId},
}; };
@@ -14,17 +14,10 @@ use crate::{
#[async_trait] #[async_trait]
pub trait SocialCommand: Send + Sync { pub trait SocialCommand: Send + Sync {
async fn follow( async fn follow(&self, follower: &UserId, target: &SocialIdentity) -> Result<(), DomainError>;
&self,
follower: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError>;
async fn unfollow( async fn unfollow(&self, follower: &UserId, target: &SocialIdentity)
&self, -> Result<(), DomainError>;
follower: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError>;
async fn accept_follow( async fn accept_follow(
&self, &self,
@@ -44,44 +37,24 @@ pub trait SocialCommand: Send + Sync {
follower: &SocialIdentity, follower: &SocialIdentity,
) -> Result<(), DomainError>; ) -> Result<(), DomainError>;
async fn block( async fn block(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError>;
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError>;
async fn unblock( async fn unblock(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError>;
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError>;
} }
#[async_trait] #[async_trait]
pub trait SocialQuery: Send + Sync { pub trait SocialQuery: Send + Sync {
async fn get_following( async fn get_following(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError>;
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError>;
async fn get_followers( async fn get_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError>;
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError>;
async fn get_pending_followers( async fn get_pending_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError>;
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError>;
async fn count_following(&self, user: &UserId) -> Result<usize, DomainError>; async fn count_following(&self, user: &UserId) -> Result<usize, DomainError>;
async fn count_followers(&self, user: &UserId) -> Result<usize, DomainError>; async fn count_followers(&self, user: &UserId) -> Result<usize, DomainError>;
async fn get_blocked( async fn get_blocked(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError>;
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError>;
async fn is_following( async fn is_following(
&self, &self,
@@ -93,7 +66,6 @@ pub trait SocialQuery: Send + Sync {
&self, &self,
user_id: &UserId, user_id: &UserId,
) -> Result<Vec<String>, DomainError>; ) -> Result<Vec<String>, DomainError>;
} }
#[async_trait] #[async_trait]

View File

@@ -893,11 +893,7 @@ impl InMemorySocialRepository {
#[async_trait] #[async_trait]
impl SocialCommand for InMemorySocialRepository { impl SocialCommand for InMemorySocialRepository {
async fn follow( async fn follow(&self, follower: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
follower: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
if let SocialIdentity::Local(target_id) = target { if let SocialIdentity::Local(target_id) = target {
if follower == target_id { if follower == target_id {
return Err(DomainError::ValidationError( return Err(DomainError::ValidationError(
@@ -925,7 +921,9 @@ impl SocialCommand for InMemorySocialRepository {
let before = store.len(); let before = store.len();
store.retain(|(f, t, _)| !(*f == follower.value() && t == target)); store.retain(|(f, t, _)| !(*f == follower.value() && t == target));
if store.len() == before { if store.len() == before {
return Err(DomainError::NotFound("Follow relationship not found".into())); return Err(DomainError::NotFound(
"Follow relationship not found".into(),
));
} }
Ok(()) Ok(())
} }
@@ -1008,11 +1006,7 @@ impl SocialCommand for InMemorySocialRepository {
Ok(()) Ok(())
} }
async fn block( async fn block(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
let mut store = self.blocked.lock().unwrap(); let mut store = self.blocked.lock().unwrap();
store.push((blocker.value(), target.clone())); store.push((blocker.value(), target.clone()));
// Also remove any existing follow relationships // Also remove any existing follow relationships
@@ -1021,11 +1015,7 @@ impl SocialCommand for InMemorySocialRepository {
Ok(()) Ok(())
} }
async fn unblock( async fn unblock(&self, blocker: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
&self,
blocker: &UserId,
target: &SocialIdentity,
) -> Result<(), DomainError> {
let mut store = self.blocked.lock().unwrap(); let mut store = self.blocked.lock().unwrap();
store.retain(|(b, t)| !(*b == blocker.value() && t == target)); store.retain(|(b, t)| !(*b == blocker.value() && t == target));
Ok(()) Ok(())
@@ -1034,10 +1024,7 @@ impl SocialCommand for InMemorySocialRepository {
#[async_trait] #[async_trait]
impl SocialQuery for InMemorySocialRepository { impl SocialQuery for InMemorySocialRepository {
async fn get_following( async fn get_following(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let store = self.follows.lock().unwrap(); let store = self.follows.lock().unwrap();
Ok(store Ok(store
.iter() .iter()
@@ -1046,10 +1033,7 @@ impl SocialQuery for InMemorySocialRepository {
.collect()) .collect())
} }
async fn get_followers( async fn get_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let store = self.follows.lock().unwrap(); let store = self.follows.lock().unwrap();
let target = SocialIdentity::Local(user.clone()); let target = SocialIdentity::Local(user.clone());
Ok(store Ok(store
@@ -1062,10 +1046,7 @@ impl SocialQuery for InMemorySocialRepository {
.collect()) .collect())
} }
async fn get_pending_followers( async fn get_pending_followers(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let store = self.follows.lock().unwrap(); let store = self.follows.lock().unwrap();
let target = SocialIdentity::Local(user.clone()); let target = SocialIdentity::Local(user.clone());
Ok(store Ok(store
@@ -1095,10 +1076,7 @@ impl SocialQuery for InMemorySocialRepository {
.count()) .count())
} }
async fn get_blocked( async fn get_blocked(&self, user: &UserId) -> Result<Vec<SocialActor>, DomainError> {
&self,
user: &UserId,
) -> Result<Vec<SocialActor>, DomainError> {
let store = self.blocked.lock().unwrap(); let store = self.blocked.lock().unwrap();
Ok(store Ok(store
.iter() .iter()
@@ -1113,9 +1091,9 @@ impl SocialQuery for InMemorySocialRepository {
target: &SocialIdentity, target: &SocialIdentity,
) -> Result<bool, DomainError> { ) -> Result<bool, DomainError> {
let store = self.follows.lock().unwrap(); let store = self.follows.lock().unwrap();
Ok(store Ok(store.iter().any(|(f, t, state)| {
.iter() *f == follower.value() && t == target && *state == FollowState::Accepted
.any(|(f, t, state)| *f == follower.value() && t == target && *state == FollowState::Accepted)) }))
} }
async fn get_accepted_following_urls( async fn get_accepted_following_urls(

View File

@@ -64,8 +64,8 @@ impl ObjectStorage for NoopObjectStorage {
// Re-export production noop types so test code that imports from // Re-export production noop types so test code that imports from
// `domain::testing` keeps compiling without changes. // `domain::testing` keeps compiling without changes.
pub use crate::ports::noop::NoopRemoteWatchlistRepository;
pub use crate::ports::noop::NoopFederationAdminQuery; pub use crate::ports::noop::NoopFederationAdminQuery;
pub use crate::ports::noop::NoopRemoteWatchlistRepository;
// ── NoopGoalCommand ─────────────────────────────────────────────────────────── // ── NoopGoalCommand ───────────────────────────────────────────────────────────

View File

@@ -6,8 +6,8 @@ use crate::{
AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId, AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId,
FeedEntry, FeedSortBy, FieldMapping, FileFormat, FollowingFilter, ImportError, FeedEntry, FeedSortBy, FieldMapping, FileFormat, FollowingFilter, ImportError,
ImportProfile, ImportSession, IndexableDocument, MovieProfile, MovieStats, ParsedFile, ImportProfile, ImportSession, IndexableDocument, MovieProfile, MovieStats, ParsedFile,
Person, PersonCredits, PersonEnrichmentData, PersonId, RefreshSession, Person, PersonCredits, PersonEnrichmentData, PersonId, RefreshSession, RemoteActorInfo,
RemoteActorInfo, ReviewHistory, SearchQuery, SearchResults, UserStats, UserTrends, ReviewHistory, SearchQuery, SearchResults, UserStats, UserTrends,
collections::{PageParams, Paginated}, collections::{PageParams, Paginated},
}, },
ports::{ ports::{

View File

@@ -2,13 +2,14 @@ use std::sync::Arc;
use domain::ports::{ use domain::ports::{
AuthService, DiaryExporter, DiaryQuery, DocumentParser, EventPublisher, FederatedProfileQuery, AuthService, DiaryExporter, DiaryQuery, DocumentParser, EventPublisher, FederatedProfileQuery,
GoalCommand, GoalQuery, ImportProfileRepository, ImportSessionRepository, MetadataClient, FederationAdminQuery, GoalCommand, GoalQuery, ImportProfileRepository, ImportSessionRepository,
MovieCommand, MovieProfileRepository, MovieQuery, ObjectStorage, PasswordHasher, PersonCommand, MetadataClient, MovieCommand, MovieProfileRepository, MovieQuery, ObjectStorage,
PersonEnrichmentClient, PersonQuery, PosterFetcherClient, RefreshSessionRepository, PasswordHasher, PersonCommand, PersonEnrichmentClient, PersonQuery, PosterFetcherClient,
RemoteGoalRepository, RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, RefreshSessionRepository, RemoteGoalRepository, RemoteWatchlistRepository, ReviewRepository,
FederationAdminQuery, SocialCommand, SocialQuery, StatsRepository, UserProfileFieldsRepository, SearchCommand, SearchPort, SocialCommand, SocialQuery, StatsRepository,
UserRepository, UserSettingsRepository, WatchEventCommand, WatchEventQuery, UserProfileFieldsRepository, UserRepository, UserSettingsRepository, WatchEventCommand,
WatchlistRepository, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery, WatchEventQuery, WatchlistRepository, WebhookTokenRepository, WrapUpRepository,
WrapUpStatsQuery,
}; };
use application::config::AppConfig; use application::config::AppConfig;

View File

@@ -84,7 +84,9 @@ pub async fn get_blocked_domains_admin(
_admin: AdminApiUser, _admin: AdminApiUser,
) -> Result<Json<Vec<BlockedDomainResponse>>, ApiError> { ) -> Result<Json<Vec<BlockedDomainResponse>>, ApiError> {
let domains = state let domains = state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.get_blocked_domains() .get_blocked_domains()
.await .await
.map_err(ap_to_domain)?; .map_err(ap_to_domain)?;
@@ -116,7 +118,9 @@ pub async fn add_blocked_domain_admin(
axum::Json(body): axum::Json<AddBlockedDomainRequest>, axum::Json(body): axum::Json<AddBlockedDomainRequest>,
) -> Result<impl IntoResponse, ApiError> { ) -> Result<impl IntoResponse, ApiError> {
state state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.add_blocked_domain(&body.domain, body.reason.as_deref()) .add_blocked_domain(&body.domain, body.reason.as_deref())
.await .await
.map_err(ap_to_domain)?; .map_err(ap_to_domain)?;
@@ -139,7 +143,9 @@ pub async fn remove_blocked_domain_admin(
axum::extract::Path(domain): axum::extract::Path<String>, axum::extract::Path(domain): axum::extract::Path<String>,
) -> Result<impl IntoResponse, ApiError> { ) -> Result<impl IntoResponse, ApiError> {
state state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.remove_blocked_domain(&domain) .remove_blocked_domain(&domain)
.await .await
.map_err(ap_to_domain)?; .map_err(ap_to_domain)?;
@@ -263,10 +269,7 @@ pub async fn get_following(
) )
.await?; .await?;
Ok(Json(ActorListResponse { Ok(Json(ActorListResponse {
actors: identities actors: identities.into_iter().map(social_actor_to_dto).collect(),
.into_iter()
.map(social_actor_to_dto)
.collect(),
})) }))
} }
@@ -293,10 +296,7 @@ pub async fn get_followers(
) )
.await?; .await?;
Ok(Json(ActorListResponse { Ok(Json(ActorListResponse {
actors: identities actors: identities.into_iter().map(social_actor_to_dto).collect(),
.into_iter()
.map(social_actor_to_dto)
.collect(),
})) }))
} }
@@ -314,10 +314,7 @@ pub async fn get_user_following(
) )
.await?; .await?;
Ok(Json(ActorListResponse { Ok(Json(ActorListResponse {
actors: identities actors: identities.into_iter().map(social_actor_to_dto).collect(),
.into_iter()
.map(social_actor_to_dto)
.collect(),
})) }))
} }
@@ -335,10 +332,7 @@ pub async fn get_user_followers(
) )
.await?; .await?;
Ok(Json(ActorListResponse { Ok(Json(ActorListResponse {
actors: identities actors: identities.into_iter().map(social_actor_to_dto).collect(),
.into_iter()
.map(social_actor_to_dto)
.collect(),
})) }))
} }
@@ -525,10 +519,7 @@ pub async fn get_pending_followers(
) )
.await?; .await?;
Ok(Json(ActorListResponse { Ok(Json(ActorListResponse {
actors: identities actors: identities.into_iter().map(social_actor_to_dto).collect(),
.into_iter()
.map(social_actor_to_dto)
.collect(),
})) }))
} }
@@ -563,7 +554,9 @@ pub async fn follow_remote_user(
&deps, &deps,
application::social::commands::FollowCommand { application::social::commands::FollowCommand {
follower_id: user_id.value(), follower_id: user_id.value(),
target: SocialIdentity::Remote { actor_url: form.handle }, target: SocialIdentity::Remote {
actor_url: form.handle,
},
}, },
) )
.await .await
@@ -604,7 +597,9 @@ pub async fn unfollow_remote_user(
&deps, &deps,
application::social::commands::UnfollowCommand { application::social::commands::UnfollowCommand {
follower_id: user_id.value(), follower_id: user_id.value(),
target: SocialIdentity::Remote { actor_url: form.actor_url }, target: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await
@@ -645,7 +640,9 @@ pub async fn accept_follower_html(
&deps, &deps,
application::social::commands::AcceptFollowCommand { application::social::commands::AcceptFollowCommand {
owner_id: user_id.value(), owner_id: user_id.value(),
requester: SocialIdentity::Remote { actor_url: form.actor_url }, requester: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await
@@ -680,7 +677,9 @@ pub async fn reject_follower_html(
&deps, &deps,
application::social::commands::RejectFollowCommand { application::social::commands::RejectFollowCommand {
owner_id: user_id.value(), owner_id: user_id.value(),
requester: SocialIdentity::Remote { actor_url: form.actor_url }, requester: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await
@@ -706,7 +705,9 @@ pub async fn get_followers_collection(
if accept.contains("application/activity+json") || accept.contains("application/ld+json") { if accept.contains("application/activity+json") || accept.contains("application/ld+json") {
let page = params.get("page").and_then(|p| p.parse::<u32>().ok()); let page = params.get("page").and_then(|p| p.parse::<u32>().ok());
return match state return match state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.followers_collection_json(user_id, page) .followers_collection_json(user_id, page)
.await .await
{ {
@@ -737,7 +738,9 @@ pub async fn get_following_collection(
if accept.contains("application/activity+json") || accept.contains("application/ld+json") { if accept.contains("application/activity+json") || accept.contains("application/ld+json") {
let page = params.get("page").and_then(|p| p.parse::<u32>().ok()); let page = params.get("page").and_then(|p| p.parse::<u32>().ok());
return match state return match state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.following_collection_json(user_id, page) .following_collection_json(user_id, page)
.await .await
{ {
@@ -776,7 +779,9 @@ pub async fn get_following_page(
}; };
match application::social::get_following::execute( match application::social::get_following::execute(
&deps, &deps,
application::social::queries::GetFollowingQuery { user_id: user_id.value() }, application::social::queries::GetFollowingQuery {
user_id: user_id.value(),
},
) )
.await .await
{ {
@@ -825,7 +830,9 @@ pub async fn get_followers_page(
}; };
match application::social::get_followers::execute( match application::social::get_followers::execute(
&deps, &deps,
application::social::queries::GetFollowersQuery { user_id: user_id.value() }, application::social::queries::GetFollowersQuery {
user_id: user_id.value(),
},
) )
.await .await
{ {
@@ -875,7 +882,9 @@ pub async fn remove_follower_html(
&deps, &deps,
application::social::commands::RemoveFollowerCommand { application::social::commands::RemoveFollowerCommand {
owner_id: user_id.value(), owner_id: user_id.value(),
follower: SocialIdentity::Remote { actor_url: form.actor_url }, follower: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await
@@ -902,7 +911,13 @@ pub async fn get_blocked_domains_page(
let mut ctx = build_page_context(&state, Some(user_id), csrf.0).await; let mut ctx = build_page_context(&state, Some(user_id), csrf.0).await;
ctx.page_title = "Blocked Domains — Movies Diary".to_string(); ctx.page_title = "Blocked Domains — Movies Diary".to_string();
ctx.canonical_url = format!("{}/admin/blocked-domains", state.app_ctx.config.base_url); ctx.canonical_url = format!("{}/admin/blocked-domains", state.app_ctx.config.base_url);
match state.app_ctx.services.ap_service.get_blocked_domains().await { match state
.app_ctx
.services
.ap_service
.get_blocked_domains()
.await
{
Ok(domains) => { Ok(domains) => {
let entries: Vec<template_askama::BlockedDomainEntry> = domains let entries: Vec<template_askama::BlockedDomainEntry> = domains
.into_iter() .into_iter()
@@ -940,7 +955,9 @@ pub async fn post_blocked_domain(
} }
let reason = form.reason.as_deref().filter(|s| !s.trim().is_empty()); let reason = form.reason.as_deref().filter(|s| !s.trim().is_empty());
match state match state
.app_ctx.services.ap_service .app_ctx
.services
.ap_service
.add_blocked_domain(&form.domain, reason) .add_blocked_domain(&form.domain, reason)
.await .await
{ {
@@ -961,7 +978,13 @@ pub async fn post_remove_blocked_domain(
if crate::csrf::mismatch(&csrf, &form.csrf_token) { if crate::csrf::mismatch(&csrf, &form.csrf_token) {
return StatusCode::FORBIDDEN.into_response(); return StatusCode::FORBIDDEN.into_response();
} }
match state.app_ctx.services.ap_service.remove_blocked_domain(&form.domain).await { match state
.app_ctx
.services
.ap_service
.remove_blocked_domain(&form.domain)
.await
{
Ok(()) => Redirect::to("/admin/blocked-domains").into_response(), Ok(()) => Redirect::to("/admin/blocked-domains").into_response(),
Err(e) => { Err(e) => {
tracing::error!("remove_blocked_domain error: {:?}", e); tracing::error!("remove_blocked_domain error: {:?}", e);
@@ -983,7 +1006,9 @@ pub async fn get_blocked_actors_page(
}; };
match application::social::get_blocked::execute( match application::social::get_blocked::execute(
&deps, &deps,
application::social::queries::GetBlockedQuery { user_id: user_id.value() }, application::social::queries::GetBlockedQuery {
user_id: user_id.value(),
},
) )
.await .await
{ {
@@ -1032,7 +1057,9 @@ pub async fn post_block_actor_html(
&deps, &deps,
application::social::commands::BlockCommand { application::social::commands::BlockCommand {
blocker_id: user_id.value(), blocker_id: user_id.value(),
target: SocialIdentity::Remote { actor_url: form.actor_url }, target: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await
@@ -1063,7 +1090,9 @@ pub async fn post_unblock_actor(
&deps, &deps,
application::social::commands::UnblockCommand { application::social::commands::UnblockCommand {
blocker_id: user_id.value(), blocker_id: user_id.value(),
target: SocialIdentity::Remote { actor_url: form.actor_url }, target: SocialIdentity::Remote {
actor_url: form.actor_url,
},
}, },
) )
.await .await

View File

@@ -66,7 +66,15 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
let event_bus = EventBusBackend::from_env()?; let event_bus = EventBusBackend::from_env()?;
#[cfg(feature = "federation")] #[cfg(feature = "federation")]
let (event_publisher_arc, ap_router, ap_service, social_query, remote_watchlist_repo, social_command_arc, social_query_unified_arc) = { let (
event_publisher_arc,
ap_router,
ap_service,
social_query,
remote_watchlist_repo,
social_command_arc,
social_query_unified_arc,
) = {
let ( let (
activity_repo, activity_repo,
follow_repo, follow_repo,

View File

@@ -1,7 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use domain::{errors::DomainError, events::DomainEvent, ports::EventHandler, value_objects::SocialIdentity}; use domain::{
errors::DomainError, events::DomainEvent, ports::EventHandler, value_objects::SocialIdentity,
};
pub struct FollowBackfillHandler { pub struct FollowBackfillHandler {
pub ap_service: Arc<dyn activitypub::ActivityPubPort>, pub ap_service: Arc<dyn activitypub::ActivityPubPort>,