feat(presentation/thoughts): use enrichment use cases — real engagement stats, no hardcoded zeros
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
use crate::{
|
||||
deps_struct,
|
||||
errors::ApiError,
|
||||
extractors::{AuthUser, Deps, FromAppState, OptionalAuthUser},
|
||||
handlers::auth::to_user_response,
|
||||
state::AppState,
|
||||
extractors::{AuthUser, Deps, OptionalAuthUser},
|
||||
handlers::feed::to_thought_response,
|
||||
};
|
||||
use api_types::{
|
||||
requests::{CreateThoughtRequest, EditThoughtRequest},
|
||||
responses::ErrorResponse,
|
||||
};
|
||||
use application::use_cases::thoughts::{
|
||||
create_thought, delete_thought, edit_thought, get_thought, get_thread, CreateThoughtInput,
|
||||
create_thought, delete_thought, edit_thought, get_thread_views, get_thought_view,
|
||||
CreateThoughtInput,
|
||||
};
|
||||
use axum::{
|
||||
extract::Path,
|
||||
@@ -18,56 +19,20 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use domain::{
|
||||
ports::{EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserRepository},
|
||||
models::feed::{EngagementStats, FeedEntry, ViewerContext},
|
||||
ports::{EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserRepository},
|
||||
value_objects::ThoughtId,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ThoughtsDeps {
|
||||
pub thoughts: Arc<dyn ThoughtRepository>,
|
||||
pub users: Arc<dyn UserRepository>,
|
||||
pub tags: Arc<dyn TagRepository>,
|
||||
pub events: Arc<dyn EventPublisher>,
|
||||
pub outbox: Arc<dyn OutboxWriter>,
|
||||
}
|
||||
|
||||
impl FromAppState for ThoughtsDeps {
|
||||
fn from_state(s: &AppState) -> Self {
|
||||
Self {
|
||||
thoughts: s.thoughts.clone(),
|
||||
users: s.users.clone(),
|
||||
tags: s.tags.clone(),
|
||||
events: s.events.clone(),
|
||||
outbox: s.outbox.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn thought_to_json(
|
||||
t: &domain::models::thought::Thought,
|
||||
author: &domain::models::user::User,
|
||||
like_count: i64,
|
||||
boost_count: i64,
|
||||
reply_count: i64,
|
||||
) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"id": t.id.as_uuid(),
|
||||
"content": t.content.as_str(),
|
||||
"author": to_user_response(author),
|
||||
"replyToId": t.in_reply_to_id.as_ref().map(|x| x.as_uuid()),
|
||||
"visibility": t.visibility.as_str(),
|
||||
"contentWarning": t.content_warning,
|
||||
"sensitive": t.sensitive,
|
||||
"likeCount": like_count,
|
||||
"boostCount": boost_count,
|
||||
"replyCount": reply_count,
|
||||
"likedByViewer": false,
|
||||
"boostedByViewer": false,
|
||||
"createdAt": t.created_at,
|
||||
"updatedAt": t.updated_at,
|
||||
})
|
||||
}
|
||||
deps_struct!(ThoughtsDeps {
|
||||
thoughts: ThoughtRepository,
|
||||
users: UserRepository,
|
||||
tags: TagRepository,
|
||||
events: EventPublisher,
|
||||
outbox: OutboxWriter,
|
||||
engagement: EngagementRepository,
|
||||
});
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/thoughts",
|
||||
@@ -106,10 +71,13 @@ pub async fn post_thought(
|
||||
.find_by_id(&uid)
|
||||
.await?
|
||||
.ok_or(domain::errors::DomainError::NotFound)?;
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(thought_to_json(&out.thought, &author, 0, 0, 0)),
|
||||
))
|
||||
let entry = FeedEntry {
|
||||
thought: out.thought,
|
||||
author,
|
||||
stats: EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 },
|
||||
viewer: Some(ViewerContext { liked: false, boosted: false }),
|
||||
};
|
||||
Ok((StatusCode::CREATED, Json(to_thought_response(&entry))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -123,15 +91,17 @@ pub async fn post_thought(
|
||||
pub async fn get_thought_handler(
|
||||
Deps(d): Deps<ThoughtsDeps>,
|
||||
Path(id): Path<Uuid>,
|
||||
OptionalAuthUser(_viewer): OptionalAuthUser,
|
||||
OptionalAuthUser(viewer): OptionalAuthUser,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let thought = get_thought(&*d.thoughts, &ThoughtId::from_uuid(id)).await?;
|
||||
let author = d
|
||||
.users
|
||||
.find_by_id(&thought.user_id)
|
||||
.await?
|
||||
.ok_or(domain::errors::DomainError::NotFound)?;
|
||||
Ok(Json(thought_to_json(&thought, &author, 0, 0, 0)))
|
||||
let entry = get_thought_view(
|
||||
&*d.thoughts,
|
||||
&*d.users,
|
||||
&*d.engagement,
|
||||
&ThoughtId::from_uuid(id),
|
||||
viewer.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(serde_json::to_value(to_thought_response(&entry)).unwrap()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -191,13 +161,19 @@ pub async fn patch_thought(
|
||||
pub async fn get_thread_handler(
|
||||
Deps(d): Deps<ThoughtsDeps>,
|
||||
Path(id): Path<Uuid>,
|
||||
OptionalAuthUser(viewer): OptionalAuthUser,
|
||||
) -> Result<Json<Vec<serde_json::Value>>, ApiError> {
|
||||
let thoughts = get_thread(&*d.thoughts, &ThoughtId::from_uuid(id)).await?;
|
||||
let mut items = Vec::new();
|
||||
for t in &thoughts {
|
||||
if let Ok(Some(author)) = d.users.find_by_id(&t.user_id).await {
|
||||
items.push(thought_to_json(t, &author, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
let entries = get_thread_views(
|
||||
&*d.thoughts,
|
||||
&*d.users,
|
||||
&*d.engagement,
|
||||
&ThoughtId::from_uuid(id),
|
||||
viewer.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
let items: Vec<_> = entries
|
||||
.iter()
|
||||
.map(|e| serde_json::to_value(to_thought_response(e)).unwrap())
|
||||
.collect();
|
||||
Ok(Json(items))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user