refactor: SocialActor rich queries, migrate remaining handlers, slim SocialQueryPort
SocialQuery returns SocialActor (identity+handle+display_name+avatar_url) instead of bare SocialIdentity. Migrated get_following_page, get_followers_page, get_blocked_actors_page to use cases. Moved get_activity_feed + get_profile from SocialQueryPort to SocialQuery. Legacy SocialQueryPort remains only for get_users listing.
This commit is contained in:
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use domain::ports::{
|
||||
DiaryQuery, EventPublisher, MovieCommand, MovieProfileRepository, MovieQuery, ReviewRepository,
|
||||
SocialQueryPort,
|
||||
SocialQuery,
|
||||
};
|
||||
|
||||
use crate::config::AppConfig;
|
||||
@@ -27,6 +27,6 @@ pub struct GetMovieSocialPageDeps {
|
||||
|
||||
pub struct GetActivityFeedDeps {
|
||||
pub diary: Arc<dyn DiaryQuery>,
|
||||
pub social_query: Arc<dyn SocialQueryPort>,
|
||||
pub social_query: Arc<dyn SocialQuery>,
|
||||
pub config: AppConfig,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::errors::DomainError;
|
||||
use domain::testing::{FakeDiaryQuery, NoopSocialQueryPort};
|
||||
use domain::testing::InMemorySocialRepository;
|
||||
use domain::value_objects::SocialActor;
|
||||
|
||||
use crate::{
|
||||
config::AppConfig, diary::deps::GetActivityFeedDeps, diary::get_activity_feed,
|
||||
@@ -11,8 +12,8 @@ use crate::{
|
||||
|
||||
fn default_deps() -> GetActivityFeedDeps {
|
||||
GetActivityFeedDeps {
|
||||
diary: FakeDiaryQuery::new() as _,
|
||||
social_query: Arc::new(NoopSocialQueryPort),
|
||||
diary: domain::testing::FakeDiaryQuery::new() as _,
|
||||
social_query: InMemorySocialRepository::new() as _,
|
||||
config: TestContextBuilder::new().config,
|
||||
}
|
||||
}
|
||||
@@ -59,20 +60,30 @@ async fn returns_feed_with_following_filter() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// NoopSocialQueryPort returns empty following, so FollowingFilter
|
||||
// contains only the viewer's id. Feed is empty but the code path is hit.
|
||||
assert!(result.items.is_empty());
|
||||
}
|
||||
|
||||
struct FakeSocialWithFollowing(Vec<String>);
|
||||
|
||||
#[async_trait]
|
||||
impl domain::ports::SocialQueryPort for FakeSocialWithFollowing {
|
||||
async fn get_accepted_following_urls(
|
||||
impl domain::ports::SocialQuery for FakeSocialWithFollowing {
|
||||
async fn get_following(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<Vec<String>, DomainError> {
|
||||
Ok(self.0.clone())
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn get_followers(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn get_pending_followers(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn count_following(
|
||||
&self,
|
||||
@@ -80,18 +91,31 @@ impl domain::ports::SocialQueryPort for FakeSocialWithFollowing {
|
||||
) -> Result<usize, DomainError> {
|
||||
Ok(0)
|
||||
}
|
||||
async fn count_accepted_followers(
|
||||
async fn count_followers(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<usize, DomainError> {
|
||||
Ok(0)
|
||||
}
|
||||
async fn get_pending_followers(
|
||||
async fn get_blocked(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<Vec<domain::models::PendingFollowerInfo>, DomainError> {
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn is_following(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
_: &domain::value_objects::SocialIdentity,
|
||||
) -> Result<bool, DomainError> {
|
||||
Ok(false)
|
||||
}
|
||||
async fn get_accepted_following_urls(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
) -> Result<Vec<String>, DomainError> {
|
||||
Ok(self.0.clone())
|
||||
}
|
||||
async fn list_all_followed_remote_actors(
|
||||
&self,
|
||||
) -> Result<Vec<domain::models::RemoteActorInfo>, DomainError> {
|
||||
@@ -112,7 +136,7 @@ async fn following_filter_parses_local_and_remote_urls() {
|
||||
let social = Arc::new(FakeSocialWithFollowing(following_urls));
|
||||
|
||||
let deps = GetActivityFeedDeps {
|
||||
diary: FakeDiaryQuery::new() as _,
|
||||
diary: domain::testing::FakeDiaryQuery::new() as _,
|
||||
social_query: social as _,
|
||||
config: AppConfig {
|
||||
allow_registration: true,
|
||||
@@ -141,8 +165,6 @@ async fn following_filter_parses_local_and_remote_urls() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Feed is empty (no data seeded), but the build_following_filter code path
|
||||
// with actual URL parsing ran without errors.
|
||||
assert!(result.items.is_empty());
|
||||
}
|
||||
|
||||
@@ -164,6 +186,5 @@ async fn following_filter_without_viewer_returns_none() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// filter_following=true but viewer_user_id=None → build_following_filter returns None
|
||||
assert!(result.items.is_empty());
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use domain::{errors::DomainError, value_objects::{SocialIdentity, UserId}};
|
||||
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetBlockedQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetBlockedQuery,
|
||||
) -> Result<Vec<SocialIdentity>, DomainError> {
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_blocked(&user_id).await
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use domain::{errors::DomainError, value_objects::{SocialIdentity, UserId}};
|
||||
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetFollowersQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetFollowersQuery,
|
||||
) -> Result<Vec<SocialIdentity>, DomainError> {
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_followers(&user_id).await
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use domain::{errors::DomainError, value_objects::{SocialIdentity, UserId}};
|
||||
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetFollowingQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetFollowingQuery,
|
||||
) -> Result<Vec<SocialIdentity>, DomainError> {
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_following(&user_id).await
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use domain::{errors::DomainError, value_objects::{SocialIdentity, UserId}};
|
||||
use domain::{errors::DomainError, value_objects::{SocialActor, UserId}};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetPendingFollowersQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetPendingFollowersQuery,
|
||||
) -> Result<Vec<SocialIdentity>, DomainError> {
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_pending_followers(&user_id).await
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ports::{
|
||||
DiaryQuery, EventPublisher, ObjectStorage, SocialQueryPort, StatsRepository, UserRepository,
|
||||
DiaryQuery, EventPublisher, ObjectStorage, SocialQuery, SocialQueryPort, StatsRepository,
|
||||
UserRepository,
|
||||
};
|
||||
|
||||
pub struct GetProfileDeps {
|
||||
pub stats: Arc<dyn StatsRepository>,
|
||||
pub diary: Arc<dyn DiaryQuery>,
|
||||
pub social_query: Arc<dyn SocialQueryPort>,
|
||||
pub social_query: Arc<dyn SocialQuery>,
|
||||
}
|
||||
|
||||
pub struct GetUsersListDeps {
|
||||
pub user: Arc<dyn UserRepository>,
|
||||
pub social_query_legacy: Arc<dyn SocialQueryPort>,
|
||||
}
|
||||
|
||||
pub struct UpdateProfileDeps {
|
||||
|
||||
@@ -86,7 +86,7 @@ async fn load_social_counts(
|
||||
.unwrap_or(0);
|
||||
let followers = deps
|
||||
.social_query
|
||||
.count_accepted_followers(user_id)
|
||||
.count_followers(user_id)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
if !is_own_profile {
|
||||
@@ -98,11 +98,19 @@ async fn load_social_counts(
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|p| PendingFollowerView {
|
||||
url: p.url,
|
||||
handle: p.handle,
|
||||
display_name: p.display_name,
|
||||
avatar_url: p.avatar_url,
|
||||
.map(|p| {
|
||||
let url = match &p.identity {
|
||||
domain::value_objects::SocialIdentity::Remote { actor_url } => actor_url.clone(),
|
||||
domain::value_objects::SocialIdentity::Local(uid) => {
|
||||
format!("local:{}", uid.value())
|
||||
}
|
||||
};
|
||||
PendingFollowerView {
|
||||
url,
|
||||
handle: p.handle,
|
||||
display_name: p.display_name,
|
||||
avatar_url: p.avatar_url,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(following, followers, pending)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::users::queries::GetUsersQuery;
|
||||
use crate::users::{deps::GetUsersListDeps, queries::GetUsersQuery};
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{RemoteActorInfo, UserSummary},
|
||||
ports::{SocialQueryPort, UserRepository},
|
||||
};
|
||||
|
||||
pub struct UsersListData {
|
||||
@@ -13,13 +10,12 @@ pub struct UsersListData {
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
user: Arc<dyn UserRepository>,
|
||||
social_query: Arc<dyn SocialQueryPort>,
|
||||
deps: &GetUsersListDeps,
|
||||
_query: GetUsersQuery,
|
||||
) -> Result<UsersListData, DomainError> {
|
||||
let (users_result, actors_result) = tokio::join!(
|
||||
user.list_with_stats(),
|
||||
social_query.list_all_followed_remote_actors()
|
||||
deps.user.list_with_stats(),
|
||||
deps.social_query_legacy.list_all_followed_remote_actors()
|
||||
);
|
||||
|
||||
Ok(UsersListData {
|
||||
@@ -27,7 +23,3 @@ pub async fn execute(
|
||||
remote_actors: actors_result?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/get_users.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -35,7 +35,7 @@ async fn returns_profile_with_empty_stats() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "profile@test.com", "profuser").await;
|
||||
@@ -70,7 +70,7 @@ async fn returns_history_view() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "hist@test.com", "histuser").await;
|
||||
@@ -107,7 +107,7 @@ async fn returns_trends_view() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "trends@test.com", "trendsuser").await;
|
||||
@@ -144,7 +144,7 @@ async fn returns_ratings_view() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "ratings@test.com", "ratingsuser").await;
|
||||
@@ -179,7 +179,7 @@ async fn returns_recent_with_search() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "search@test.com", "searchuser").await;
|
||||
@@ -214,7 +214,7 @@ async fn non_own_profile_skips_pending_followers() {
|
||||
let deps = GetProfileDeps {
|
||||
stats: b.stats_repo.clone(),
|
||||
diary: b.diary_repo.clone(),
|
||||
social_query: b.social_query.clone(),
|
||||
social_query: b.social_query_unified.clone(),
|
||||
};
|
||||
|
||||
setup_user(&b, "other@test.com", "otheruser").await;
|
||||
|
||||
Reference in New Issue
Block a user