diff --git a/crates/application/src/use_cases/feed.rs b/crates/application/src/use_cases/feed.rs index 176e346..22b4489 100644 --- a/crates/application/src/use_cases/feed.rs +++ b/crates/application/src/use_cases/feed.rs @@ -4,7 +4,7 @@ use domain::{ feed::{FeedEntry, PageParams, Paginated, UserSummary}, user::User, }, - ports::{FeedRepository, FollowRepository, UserRepository}, + ports::{FeedRepository, FollowRepository, TagRepository, UserRepository}, value_objects::UserId, }; @@ -40,3 +40,7 @@ pub async fn search(feed: &dyn FeedRepository, query: &str, page: PageParams, vi pub async fn list_users(users: &dyn UserRepository) -> Result, DomainError> { users.list_with_stats().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/mod.rs b/crates/application/src/use_cases/mod.rs index 8b8f07e..ad33883 100644 --- a/crates/application/src/use_cases/mod.rs +++ b/crates/application/src/use_cases/mod.rs @@ -1,6 +1,8 @@ pub mod api_keys; pub mod auth; pub mod feed; +pub mod notifications; pub mod profile; +pub mod search; pub mod social; pub mod thoughts; diff --git a/crates/application/src/use_cases/notifications.rs b/crates/application/src/use_cases/notifications.rs new file mode 100644 index 0000000..219404f --- /dev/null +++ b/crates/application/src/use_cases/notifications.rs @@ -0,0 +1,30 @@ +use domain::{ + errors::DomainError, + models::feed::{PageParams, Paginated}, + models::notification::Notification, + ports::NotificationRepository, + value_objects::{NotificationId, UserId}, +}; + +pub async fn list_notifications( + repo: &dyn NotificationRepository, + user_id: &UserId, + page: PageParams, +) -> Result, DomainError> { + repo.list_for_user(user_id, &page).await +} + +pub async fn mark_notification_read( + repo: &dyn NotificationRepository, + id: &NotificationId, + user_id: &UserId, +) -> Result<(), DomainError> { + repo.mark_read(id, user_id).await +} + +pub async fn mark_all_notifications_read( + repo: &dyn NotificationRepository, + user_id: &UserId, +) -> Result<(), DomainError> { + repo.mark_all_read(user_id).await +} diff --git a/crates/application/src/use_cases/search.rs b/crates/application/src/use_cases/search.rs new file mode 100644 index 0000000..00b05cc --- /dev/null +++ b/crates/application/src/use_cases/search.rs @@ -0,0 +1,26 @@ +use domain::{ + errors::DomainError, + models::{ + feed::{FeedEntry, PageParams, Paginated}, + user::User, + }, + ports::SearchPort, + value_objects::UserId, +}; + +pub async fn search_thoughts( + search: &dyn SearchPort, + query: &str, + page: PageParams, + viewer_id: Option<&UserId>, +) -> Result, DomainError> { + search.search_thoughts(query, &page, viewer_id).await +} + +pub async fn search_users( + search: &dyn SearchPort, + query: &str, + page: PageParams, +) -> Result, DomainError> { + search.search_users(query, &page).await +} diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs index 297b8fa..a1f679e 100644 --- a/crates/presentation/src/handlers/feed.rs +++ b/crates/presentation/src/handlers/feed.rs @@ -1,7 +1,8 @@ use axum::{extract::{Path, Query, State}, Json}; use api_types::requests::{PaginationQuery, SearchQuery}; use api_types::responses::ThoughtResponse; -use application::use_cases::feed::{get_home_feed, get_public_feed, get_followers, get_following, get_user_feed, get_by_tag}; +use application::use_cases::feed::{get_home_feed, get_public_feed, get_followers, get_following, get_user_feed, get_by_tag, get_popular_tags as uc_get_popular_tags}; +use application::use_cases::search::{search_thoughts, search_users}; use domain::models::feed::PageParams; use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState}; use application::use_cases::profile::get_user_by_username; @@ -72,8 +73,8 @@ pub async fn search_handler( let query = q.q.trim().to_string(); let (thoughts_result, users_result) = tokio::join!( - s.search.search_thoughts(&query, &page, viewer.as_ref()), - s.search.search_users(&query, &page), + search_thoughts(&*s.search, &query, PageParams { page: page.page, per_page: page.per_page }, viewer.as_ref()), + search_users(&*s.search, &query, PageParams { page: page.page, per_page: page.per_page }), ); let thoughts = thoughts_result?.items.into_iter().map(|e| serde_json::json!({ @@ -139,7 +140,7 @@ pub async fn get_popular_tags( Query(params): Query>, ) -> Result, ApiError> { let limit: usize = params.get("limit").and_then(|v| v.parse().ok()).unwrap_or(20); - let tags = s.tags.popular_tags(limit.min(100)).await?; + let tags = uc_get_popular_tags(&*s.tags, limit.min(100)).await?; Ok(Json(serde_json::json!({ "tags": tags.iter().map(|(name, count)| serde_json::json!({ "name": name, diff --git a/crates/presentation/src/handlers/notifications.rs b/crates/presentation/src/handlers/notifications.rs index 779b399..91bd6a3 100644 --- a/crates/presentation/src/handlers/notifications.rs +++ b/crates/presentation/src/handlers/notifications.rs @@ -1,21 +1,28 @@ use axum::{extract::{Path, State}, http::StatusCode, Json}; use uuid::Uuid; use domain::{models::feed::PageParams, value_objects::NotificationId}; +use application::use_cases::notifications::{ + list_notifications as uc_list_notifications, + mark_notification_read as uc_mark_notification_read, + mark_all_notifications_read, +}; use crate::{errors::ApiError, extractors::AuthUser, state::AppState}; #[utoipa::path(get, path = "/notifications", responses((status = 200, description = "Notification summary")), security(("bearer_auth" = [])))] pub async fn list_notifications(State(s): State, AuthUser(uid): AuthUser) -> Result, ApiError> { let page = PageParams { page: 1, per_page: 20 }; - let result = s.notifications.list_for_user(&uid, &page).await?; + let result = uc_list_notifications(&*s.notifications, &uid, page).await?; Ok(Json(serde_json::json!({ "total": result.total, "unread": result.items.iter().filter(|n| !n.read).count() }))) } + #[utoipa::path(post, path = "/notifications/{id}/read", params(("id" = uuid::Uuid, Path, description = "Notification ID")), responses((status = 204, description = "Marked read")), security(("bearer_auth" = [])))] pub async fn mark_notification_read(State(s): State, AuthUser(uid): AuthUser, Path(id): Path) -> Result { - s.notifications.mark_read(&NotificationId::from_uuid(id), &uid).await?; + uc_mark_notification_read(&*s.notifications, &NotificationId::from_uuid(id), &uid).await?; Ok(StatusCode::NO_CONTENT) } + #[utoipa::path(post, path = "/notifications/read-all", responses((status = 204, description = "All marked read")), security(("bearer_auth" = [])))] pub async fn mark_all_read(State(s): State, AuthUser(uid): AuthUser) -> Result { - s.notifications.mark_all_read(&uid).await?; + mark_all_notifications_read(&*s.notifications, &uid).await?; Ok(StatusCode::NO_CONTENT) } diff --git a/crates/presentation/src/handlers/users.rs b/crates/presentation/src/handlers/users.rs index c235cdb..b21419b 100644 --- a/crates/presentation/src/handlers/users.rs +++ b/crates/presentation/src/handlers/users.rs @@ -1,6 +1,8 @@ use axum::{extract::{Path, Query, State}, Json}; use api_types::{requests::UpdateProfileRequest, responses::{ErrorResponse, UserResponse}}; use application::use_cases::profile::{get_user_by_username, update_profile}; +use application::use_cases::search::search_users; +use application::use_cases::feed::list_users; use crate::{errors::ApiError, extractors::AuthUser, handlers::auth::to_user_response, state::AppState}; #[utoipa::path( @@ -54,14 +56,14 @@ pub async fn get_users( let page_params = PageParams { page, per_page }; if let Some(q) = params.get("q").filter(|q| !q.trim().is_empty()) { - let result = s.search.search_users(q, &page_params).await?; + let result = search_users(&*s.search, q, page_params).await?; let users: Vec<_> = result.items.iter().map(|u| crate::handlers::auth::to_user_response(u)).collect(); return Ok(Json(serde_json::json!({ "items": users, "total": result.total, "page": result.page, "per_page": result.per_page }))); } - let all = s.users.list_with_stats().await?; + let all = list_users(&*s.users).await?; let total = all.len() as i64; let start = ((page - 1) * per_page) as usize; let items: Vec<_> = all.into_iter()