refactor: wrap direct port calls behind use cases — notifications, search, popular_tags
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 5m7s
test / unit (pull_request) Successful in 15m51s
test / integration (pull_request) Failing after 17m3s
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 5m7s
test / unit (pull_request) Successful in 15m51s
test / integration (pull_request) Failing after 17m3s
This commit is contained in:
@@ -4,7 +4,7 @@ use domain::{
|
|||||||
feed::{FeedEntry, PageParams, Paginated, UserSummary},
|
feed::{FeedEntry, PageParams, Paginated, UserSummary},
|
||||||
user::User,
|
user::User,
|
||||||
},
|
},
|
||||||
ports::{FeedRepository, FollowRepository, UserRepository},
|
ports::{FeedRepository, FollowRepository, TagRepository, UserRepository},
|
||||||
value_objects::UserId,
|
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<Vec<UserSummary>, DomainError> {
|
pub async fn list_users(users: &dyn UserRepository) -> Result<Vec<UserSummary>, DomainError> {
|
||||||
users.list_with_stats().await
|
users.list_with_stats().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_popular_tags(tags: &dyn TagRepository, limit: usize) -> Result<Vec<(String, i64)>, DomainError> {
|
||||||
|
tags.popular_tags(limit).await
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
pub mod api_keys;
|
pub mod api_keys;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod feed;
|
pub mod feed;
|
||||||
|
pub mod notifications;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
|
pub mod search;
|
||||||
pub mod social;
|
pub mod social;
|
||||||
pub mod thoughts;
|
pub mod thoughts;
|
||||||
|
|||||||
30
crates/application/src/use_cases/notifications.rs
Normal file
30
crates/application/src/use_cases/notifications.rs
Normal file
@@ -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<Paginated<Notification>, 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
|
||||||
|
}
|
||||||
26
crates/application/src/use_cases/search.rs
Normal file
26
crates/application/src/use_cases/search.rs
Normal file
@@ -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<Paginated<FeedEntry>, DomainError> {
|
||||||
|
search.search_thoughts(query, &page, viewer_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn search_users(
|
||||||
|
search: &dyn SearchPort,
|
||||||
|
query: &str,
|
||||||
|
page: PageParams,
|
||||||
|
) -> Result<Paginated<User>, DomainError> {
|
||||||
|
search.search_users(query, &page).await
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
use axum::{extract::{Path, Query, State}, Json};
|
use axum::{extract::{Path, Query, State}, Json};
|
||||||
use api_types::requests::{PaginationQuery, SearchQuery};
|
use api_types::requests::{PaginationQuery, SearchQuery};
|
||||||
use api_types::responses::ThoughtResponse;
|
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 domain::models::feed::PageParams;
|
||||||
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
||||||
use application::use_cases::profile::get_user_by_username;
|
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 query = q.q.trim().to_string();
|
||||||
|
|
||||||
let (thoughts_result, users_result) = tokio::join!(
|
let (thoughts_result, users_result) = tokio::join!(
|
||||||
s.search.search_thoughts(&query, &page, viewer.as_ref()),
|
search_thoughts(&*s.search, &query, PageParams { page: page.page, per_page: page.per_page }, viewer.as_ref()),
|
||||||
s.search.search_users(&query, &page),
|
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!({
|
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<std::collections::HashMap<String, String>>,
|
Query(params): Query<std::collections::HashMap<String, String>>,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let limit: usize = params.get("limit").and_then(|v| v.parse().ok()).unwrap_or(20);
|
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!({
|
Ok(Json(serde_json::json!({
|
||||||
"tags": tags.iter().map(|(name, count)| serde_json::json!({
|
"tags": tags.iter().map(|(name, count)| serde_json::json!({
|
||||||
"name": name,
|
"name": name,
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use domain::{models::feed::PageParams, value_objects::NotificationId};
|
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};
|
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
||||||
|
|
||||||
#[utoipa::path(get, path = "/notifications", responses((status = 200, description = "Notification summary")), security(("bearer_auth" = [])))]
|
#[utoipa::path(get, path = "/notifications", responses((status = 200, description = "Notification summary")), security(("bearer_auth" = [])))]
|
||||||
pub async fn list_notifications(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn list_notifications(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let page = PageParams { page: 1, per_page: 20 };
|
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() })))
|
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" = [])))]
|
#[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<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn mark_notification_read(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
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)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(post, path = "/notifications/read-all", responses((status = 204, description = "All marked read")), security(("bearer_auth" = [])))]
|
#[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<AppState>, AuthUser(uid): AuthUser) -> Result<StatusCode, ApiError> {
|
pub async fn mark_all_read(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<StatusCode, ApiError> {
|
||||||
s.notifications.mark_all_read(&uid).await?;
|
mark_all_notifications_read(&*s.notifications, &uid).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use axum::{extract::{Path, Query, State}, Json};
|
use axum::{extract::{Path, Query, State}, Json};
|
||||||
use api_types::{requests::UpdateProfileRequest, responses::{ErrorResponse, UserResponse}};
|
use api_types::{requests::UpdateProfileRequest, responses::{ErrorResponse, UserResponse}};
|
||||||
use application::use_cases::profile::{get_user_by_username, update_profile};
|
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};
|
use crate::{errors::ApiError, extractors::AuthUser, handlers::auth::to_user_response, state::AppState};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
@@ -54,14 +56,14 @@ pub async fn get_users(
|
|||||||
let page_params = PageParams { page, per_page };
|
let page_params = PageParams { page, per_page };
|
||||||
|
|
||||||
if let Some(q) = params.get("q").filter(|q| !q.trim().is_empty()) {
|
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();
|
let users: Vec<_> = result.items.iter().map(|u| crate::handlers::auth::to_user_response(u)).collect();
|
||||||
return Ok(Json(serde_json::json!({
|
return Ok(Json(serde_json::json!({
|
||||||
"items": users, "total": result.total, "page": result.page, "per_page": result.per_page
|
"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 total = all.len() as i64;
|
||||||
let start = ((page - 1) * per_page) as usize;
|
let start = ((page - 1) * per_page) as usize;
|
||||||
let items: Vec<_> = all.into_iter()
|
let items: Vec<_> = all.into_iter()
|
||||||
|
|||||||
Reference in New Issue
Block a user