diff --git a/crates/application/src/use_cases/federation_management/mod.rs b/crates/application/src/use_cases/federation_management/mod.rs index f921223..b75ae66 100644 --- a/crates/application/src/use_cases/federation_management/mod.rs +++ b/crates/application/src/use_cases/federation_management/mod.rs @@ -10,7 +10,7 @@ use domain::{ ports::{ EventPublisher, FederationActionPort, FederationFollowPort, FederationFollowRequestPort, FederationSchedulerPort, FeedOptions, FeedQuery, FeedRepository, FeedRequest, - FollowRepository, RemoteActorConnectionRepository, UserReader, + FollowRepository, RemoteActorConnectionRepository, UserReader, UserWriter, }, value_objects::UserId, }; @@ -187,5 +187,13 @@ pub async fn get_actor_connections_page( Ok((items, has_more)) } +pub async fn set_also_known_as( + users: &dyn UserWriter, + user_id: &UserId, + value: Option, +) -> Result<(), DomainError> { + users.set_also_known_as(user_id, value).await +} + #[cfg(test)] mod tests; diff --git a/crates/application/src/use_cases/feed.rs b/crates/application/src/use_cases/feed.rs index b45235d..78543bf 100644 --- a/crates/application/src/use_cases/feed.rs +++ b/crates/application/src/use_cases/feed.rs @@ -1,7 +1,7 @@ use domain::{ errors::DomainError, models::feed::{FeedEntry, PageParams, Paginated}, - ports::{FeedOptions, FeedQuery, FeedRepository, FeedRequest, FollowRepository}, + ports::{FeedOptions, FeedQuery, FeedRepository, FeedRequest, FollowRepository, TagRepository}, value_objects::UserId, }; @@ -20,3 +20,51 @@ pub async fn get_home_feed( }) .await } + +pub async fn get_public_feed( + feed: &dyn FeedRepository, + viewer: Option, + page: PageParams, + opts: FeedOptions, +) -> Result, DomainError> { + feed.query(&FeedRequest { + query: FeedQuery::public(page, viewer), + options: opts, + }) + .await +} + +pub async fn get_user_feed( + feed: &dyn FeedRepository, + user_id: UserId, + page: PageParams, + opts: FeedOptions, + viewer: Option, +) -> Result, DomainError> { + feed.query(&FeedRequest { + query: FeedQuery::user(user_id, page, viewer), + options: opts, + }) + .await +} + +pub async fn get_tag_feed( + feed: &dyn FeedRepository, + tag: &str, + page: PageParams, + opts: FeedOptions, + viewer: Option, +) -> Result, DomainError> { + feed.query(&FeedRequest { + query: FeedQuery::tag(tag, page, viewer), + options: opts, + }) + .await +} + +pub async fn get_popular_tags( + tags: &dyn TagRepository, + limit: usize, +) -> Result, DomainError> { + tags.popular_tags(limit).await +} diff --git a/crates/application/src/use_cases/profile/mod.rs b/crates/application/src/use_cases/profile/mod.rs index 4b0bec3..30c9e15 100644 --- a/crates/application/src/use_cases/profile/mod.rs +++ b/crates/application/src/use_cases/profile/mod.rs @@ -5,11 +5,13 @@ use domain::{ errors::DomainError, events::DomainEvent, models::{ + feed::{PageParams, Paginated, UserSummary}, top_friend::TopFriend, user::{UpdateProfileInput, User}, }, ports::{ - EventPublisher, MediaStore, TopFriendRepository, UserReader, UserRepository, UserWriter, + EventPublisher, FollowRepository, MediaStore, TopFriendRepository, UserReader, + UserRepository, UserWriter, }, value_objects::{UserId, Username}, }; @@ -230,5 +232,46 @@ pub async fn upload_banner( .await } +pub async fn get_user_profile( + users: &dyn UserReader, + follows: &dyn FollowRepository, + id_or_username: &str, + viewer_id: Option<&UserId>, +) -> Result<(User, bool), DomainError> { + let user = get_user_by_id_or_username(users, id_or_username).await?; + let is_followed = match viewer_id { + Some(vid) if vid != &user.id => follows.find(vid, &user.id).await?.is_some(), + _ => false, + }; + Ok((user, is_followed)) +} + +pub async fn list_users( + users: &dyn UserReader, + page: PageParams, +) -> Result, DomainError> { + users.list_paginated(page).await +} + +pub async fn count_local_users(users: &dyn UserReader) -> Result { + users.count().await +} + +pub async fn list_local_followers( + follows: &dyn FollowRepository, + user_id: &UserId, + page: PageParams, +) -> Result, DomainError> { + follows.list_followers(user_id, &page).await +} + +pub async fn list_local_following( + follows: &dyn FollowRepository, + user_id: &UserId, + page: PageParams, +) -> Result, DomainError> { + follows.list_following(user_id, &page).await +} + #[cfg(test)] mod tests; diff --git a/crates/presentation/src/handlers/federation_management.rs b/crates/presentation/src/handlers/federation_management.rs index fca2c44..97c39d8 100644 --- a/crates/presentation/src/handlers/federation_management.rs +++ b/crates/presentation/src/handlers/federation_management.rs @@ -7,6 +7,7 @@ use api_types::responses::{ErrorResponse, ProfileField, RemoteActorResponse}; use application::use_cases::federation_management::{ accept_follow_request, get_remote_friends, initiate_actor_move, list_pending_requests, list_remote_followers, list_remote_following, reject_follow_request, remove_remote_following, + set_also_known_as, }; use axum::{http::StatusCode, Json}; use domain::ports::{EventPublisher, FederationActionPort, FollowRepository, UserRepository}; @@ -208,6 +209,6 @@ pub async fn patch_also_known_as( AuthUser(uid): AuthUser, Json(body): Json, ) -> Result { - d.users.set_also_known_as(&uid, body.also_known_as).await?; + set_also_known_as(&*d.users, &uid, body.also_known_as).await?; Ok(StatusCode::NO_CONTENT) } diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs index 4a237d0..63d832b 100644 --- a/crates/presentation/src/handlers/feed.rs +++ b/crates/presentation/src/handlers/feed.rs @@ -6,8 +6,13 @@ use crate::{ }; use api_types::requests::{PaginationQuery, SearchQuery}; use api_types::responses::ThoughtResponse; -use application::use_cases::feed::get_home_feed; -use application::use_cases::profile::{get_user_by_id_or_username, get_user_by_username}; +use application::use_cases::feed::{ + get_home_feed, get_popular_tags as uc_get_popular_tags, get_public_feed, get_tag_feed, + get_user_feed, +}; +use application::use_cases::profile::{ + get_user_by_id_or_username, get_user_by_username, list_local_followers, list_local_following, +}; use axum::{ extract::{Path, Query}, http::{header, HeaderMap}, @@ -17,8 +22,8 @@ use axum::{ use domain::{ models::feed::PageParams, ports::{ - FederationActionPort, FeedFilter, FeedOptions, FeedQuery, FeedRepository, FeedRequest, - FeedSort, FollowRepository, SearchPort, TagRepository, UserRepository, + FederationActionPort, FeedFilter, FeedOptions, FeedRepository, FeedSort, FollowRepository, + SearchPort, TagRepository, UserRepository, }, }; @@ -142,13 +147,7 @@ pub async fn public_feed( per_page: q.per_page(), }; let opts = FeedOptions::try_from(opts_q)?; - let result = d - .feed - .query(&FeedRequest { - query: FeedQuery::public(page, viewer), - options: opts, - }) - .await?; + let result = get_public_feed(&*d.feed, viewer, page, opts).await?; Ok(Json(serde_json::json!({ "items": result.items.iter().map(to_thought_response).collect::>(), "total": result.total, @@ -232,7 +231,7 @@ pub async fn get_following_handler( page: q.page(), per_page: q.per_page(), }; - let result = d.follows.list_following(&user.id, &page).await?; + let result = list_local_following(&*d.follows, &user.id, page).await?; Ok(Json(serde_json::json!({ "total": result.total, "items": result.items.iter().map(to_user_response).collect::>() @@ -275,7 +274,7 @@ pub async fn get_followers_handler( page: q.page(), per_page: q.per_page(), }; - let result = d.follows.list_followers(&user.id, &page).await?; + let result = list_local_followers(&*d.follows, &user.id, page).await?; Ok(Json(serde_json::json!({ "total": result.total, "items": result.items.iter().map(to_user_response).collect::>() @@ -305,13 +304,7 @@ pub async fn user_thoughts_handler( per_page: q.per_page(), }; let opts = FeedOptions::try_from(opts_q)?; - let result = d - .feed - .query(&FeedRequest { - query: FeedQuery::user(user.id.clone(), page, viewer), - options: opts, - }) - .await?; + let result = get_user_feed(&*d.feed, user.id.clone(), page, opts, viewer).await?; Ok(Json(serde_json::json!({ "total": result.total, "page": result.page, @@ -334,11 +327,9 @@ pub async fn get_popular_tags( let limit: usize = params .get("limit") .and_then(|v| v.parse().ok()) - .unwrap_or(api_types::requests::DEFAULT_PER_PAGE as usize); - let tags = d - .tags - .popular_tags(limit.min(api_types::requests::MAX_PER_PAGE as usize)) - .await?; + .unwrap_or(api_types::requests::DEFAULT_PER_PAGE as usize) + .min(api_types::requests::MAX_PER_PAGE as usize); + let tags = uc_get_popular_tags(&*d.tags, limit).await?; Ok(Json(serde_json::json!({ "tags": tags.iter().map(|(name, count)| serde_json::json!({ "name": name, @@ -368,13 +359,7 @@ pub async fn tag_thoughts_handler( per_page: q.per_page(), }; let opts = FeedOptions::try_from(opts_q)?; - let result = d - .feed - .query(&FeedRequest { - query: FeedQuery::tag(&tag_name, page, viewer), - options: opts, - }) - .await?; + let result = get_tag_feed(&*d.feed, &tag_name, page, opts, viewer).await?; Ok(Json(serde_json::json!({ "tag": tag_name, "total": result.total, diff --git a/crates/presentation/src/handlers/users/mod.rs b/crates/presentation/src/handlers/users/mod.rs index 91eeb15..76cfaa5 100644 --- a/crates/presentation/src/handlers/users/mod.rs +++ b/crates/presentation/src/handlers/users/mod.rs @@ -9,8 +9,9 @@ use api_types::{ responses::{ErrorResponse, ProfileField, RemoteActorResponse, UserResponse}, }; use application::use_cases::profile::{ - get_user as fetch_user, get_user_by_id_or_username, update_profile, - upload_avatar as upload_avatar_uc, upload_banner as upload_banner_uc, UploadConfig, + count_local_users, get_user as fetch_user, get_user_by_id_or_username, get_user_profile, + list_local_following, list_users, update_profile, upload_avatar as upload_avatar_uc, + upload_banner as upload_banner_uc, UploadConfig, }; use axum::{ extract::{Multipart, Path, Query}, @@ -67,22 +68,18 @@ pub async fn get_user( OptionalAuthUser(viewer): OptionalAuthUser, headers: HeaderMap, ) -> Result { - let user = get_user_by_id_or_username(&*d.users, &username).await?; - let accept = headers .get(header::ACCEPT) .and_then(|v| v.to_str().ok()) .unwrap_or(""); if accept.contains("application/activity+json") { + let user = get_user_by_id_or_username(&*d.users, &username).await?; let json = d.federation.actor_json(&user.id).await?; Ok(([(header::CONTENT_TYPE, "application/activity+json")], json).into_response()) } else { - let is_followed = if let Some(viewer_id) = viewer { - d.follows.find(&viewer_id, &user.id).await?.is_some() - } else { - false - }; + let (user, is_followed) = + get_user_profile(&*d.users, &*d.follows, &username, viewer.as_ref()).await?; let mut resp = to_user_response(&user); resp.is_followed_by_viewer = is_followed; Ok(Json(resp).into_response()) @@ -154,7 +151,7 @@ pub async fn get_me_following( page: q.page(), per_page: q.per_page(), }; - let result = d.follows.list_following(&uid, &page).await?; + let result = list_local_following(&*d.follows, &uid, page).await?; Ok(Json(serde_json::json!({ "total": result.total, "items": result.items.iter().map(to_user_response).collect::>(), @@ -197,7 +194,7 @@ pub async fn get_users( }))); } - let result = d.users.list_paginated(page_params).await?; + let result = list_users(&*d.users, page_params).await?; let items: Vec<_> = result .items .iter() @@ -226,7 +223,7 @@ pub async fn get_users( responses((status = 200, description = "Total number of local users")) )] pub async fn get_user_count(Deps(d): Deps) -> Result, ApiError> { - let count = d.users.count().await?; + let count = count_local_users(&*d.users).await?; Ok(Json(serde_json::json!({ "count": count }))) } diff --git a/crates/presentation/src/openapi/feed.rs b/crates/presentation/src/openapi/feed.rs index c8bf35c..7875981 100644 --- a/crates/presentation/src/openapi/feed.rs +++ b/crates/presentation/src/openapi/feed.rs @@ -6,6 +6,9 @@ use utoipa::OpenApi; crate::handlers::feed::public_feed, crate::handlers::feed::search_handler, crate::handlers::feed::user_thoughts_handler, + crate::handlers::feed::get_followers_handler, + crate::handlers::feed::get_following_handler, crate::handlers::feed::tag_thoughts_handler, + crate::handlers::feed::get_popular_tags, ))] pub struct FeedDoc; diff --git a/crates/presentation/src/openapi/users.rs b/crates/presentation/src/openapi/users.rs index df6fd62..c8a7d88 100644 --- a/crates/presentation/src/openapi/users.rs +++ b/crates/presentation/src/openapi/users.rs @@ -7,9 +7,15 @@ use utoipa::OpenApi; #[derive(OpenApi)] #[openapi( paths( + crate::handlers::users::get_users, + crate::handlers::users::get_user_count, + crate::handlers::users::lookup_handler, crate::handlers::users::get_me, + crate::handlers::users::get_me_following, crate::handlers::users::get_user, crate::handlers::users::patch_profile, + crate::handlers::users::upload_avatar, + crate::handlers::users::upload_banner, ), components(schemas(UserResponse, UpdateProfileRequest, ErrorResponse)) )]