use super::{PgPool, Uuid}; #[derive(sqlx::FromRow)] pub struct EndorsementRow { pub post_id: Uuid, pub endorser_id: Uuid, } /// One post's total endorsement count (from an aggregated `GROUP BY`). #[derive(sqlx::FromRow)] pub struct EndorsementCount { pub post_id: Uuid, pub count: i64, } /// Batch-fetch endorsements for a set of post IDs. /// /// Loads one row per endorsement, fine when the caller needs the endorser ids, /// but the thread view must NOT use this: a hot post with thousands of /// endorsements would stream every row on every (unauthenticated, unthrottled) /// page load. That path uses [`count_endorsements_for_posts`] + /// [`list_user_endorsed_posts`] instead. #[tracing::instrument(skip_all)] pub async fn list_endorsements_for_posts( pool: &PgPool, post_ids: &[Uuid], ) -> Result, sqlx::Error> { sqlx::query_as!( EndorsementRow, "SELECT post_id, endorser_id FROM post_endorsements WHERE post_id = ANY($1)", post_ids, ) .fetch_all(pool) .await } /// Per-post endorsement counts, aggregated in Postgres. /// /// Replaces streaming every endorsement row into the app just to `len()` them: /// a `GROUP BY` returns at most one row per visible post regardless of how many /// endorsements a post has (fuzz-2026-07-06 endorsement fan-out). Posts with /// zero endorsements are absent; the caller defaults them to 0. #[tracing::instrument(skip_all)] pub async fn count_endorsements_for_posts( pool: &PgPool, post_ids: &[Uuid], ) -> Result, sqlx::Error> { sqlx::query_as!( EndorsementCount, r#"SELECT post_id, COUNT(*) AS "count!" FROM post_endorsements WHERE post_id = ANY($1) GROUP BY post_id"#, post_ids, ) .fetch_all(pool) .await } /// The subset of `post_ids` the given user has endorsed. Bounded by the page's /// post count (not the endorsement total), so the thread view can mark the /// viewer's own endorsements without loading every endorser id. #[tracing::instrument(skip_all)] pub async fn list_user_endorsed_posts( pool: &PgPool, post_ids: &[Uuid], user_id: Uuid, ) -> Result, sqlx::Error> { sqlx::query_scalar!( "SELECT post_id FROM post_endorsements WHERE post_id = ANY($1) AND endorser_id = $2", post_ids, user_id, ) .fetch_all(pool) .await }