fix: move user_feed to FeedRepository — proper counts and viewer flags for user timelines

This commit is contained in:
2026-05-14 16:06:38 +02:00
parent ecba9267cf
commit 970f5a1644
6 changed files with 59 additions and 40 deletions

View File

@@ -196,6 +196,36 @@ impl FeedRepository for PgFeedRepository {
per_page: page.per_page,
})
}
async fn user_feed(&self, user_id: &UserId, page: &PageParams, viewer_id: Option<&UserId>) -> Result<Paginated<FeedEntry>, DomainError> {
let viewer = viewer_id.map(|v| v.as_uuid());
let uid = user_id.as_uuid();
let total: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM thoughts t WHERE t.user_id = $1 AND t.visibility = 'public'"
)
.bind(uid)
.fetch_one(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
let sel = feed_select(viewer);
let sql = format!("{sel} WHERE t.user_id = $1 AND t.visibility = 'public' ORDER BY t.created_at DESC LIMIT $2 OFFSET $3");
let rows = sqlx::query_as::<_, FeedRow>(&sql)
.bind(uid)
.bind(page.limit())
.bind(page.offset())
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
Ok(Paginated {
items: rows.into_iter().map(row_to_entry).collect(),
total,
page: page.page,
per_page: page.per_page,
})
}
}
#[cfg(test)]

View File

@@ -4,12 +4,11 @@ use sqlx::PgPool;
use domain::{
errors::DomainError,
models::{
feed::{FeedEntry, PageParams, Paginated},
feed::{PageParams, Paginated},
thought::{Thought, Visibility},
user::User,
},
ports::ThoughtRepository,
value_objects::{Content, Email, PasswordHash, ThoughtId, UserId, Username},
value_objects::{Content, ThoughtId, UserId},
};
pub struct PgThoughtRepository { pool: PgPool }
@@ -119,47 +118,32 @@ impl ThoughtRepository for PgThoughtRepository {
.map(|rows| rows.into_iter().map(Thought::from).collect())
}
async fn list_by_user(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<FeedEntry>, DomainError> {
let total: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM thoughts WHERE user_id=$1")
.bind(user_id.as_uuid())
.fetch_one(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
async fn list_by_user(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<Thought>, DomainError> {
let uid = user_id.as_uuid();
let total: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM thoughts WHERE user_id = $1"
)
.bind(uid)
.fetch_one(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
let rows = sqlx::query_as::<_, ThoughtRow>(
&format!("{THOUGHT_SELECT} WHERE user_id=$1 ORDER BY created_at DESC LIMIT $2 OFFSET $3")
)
.bind(user_id.as_uuid())
.bind(uid)
.bind(page.limit())
.bind(page.offset())
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
let author = sqlx::query_as::<_, crate::user::UserRow>(
"SELECT id,username,email,password_hash,display_name,bio,avatar_url,header_url,custom_css,local,ap_id,inbox_url,public_key,private_key,created_at,updated_at FROM users WHERE id=$1"
)
.bind(user_id.as_uuid())
.fetch_optional(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?
.ok_or(DomainError::NotFound)?;
let author = User::from(author);
let items = rows.into_iter().map(|r| {
let thought = Thought::from(r);
FeedEntry {
thought,
author: author.clone(),
like_count: 0,
boost_count: 0,
reply_count: 0,
liked_by_viewer: false,
boosted_by_viewer: false,
}
}).collect();
Ok(Paginated { items, total, page: page.page, per_page: page.per_page })
Ok(Paginated {
items: rows.into_iter().map(Thought::from).collect(),
total,
page: page.page,
per_page: page.per_page,
})
}
}