Skip to main content

max / makenotwork

2.4 KB · 77 lines History Blame Raw
1 use super::{PgPool, Uuid};
2
3 #[derive(sqlx::FromRow)]
4 pub struct EndorsementRow {
5 pub post_id: Uuid,
6 pub endorser_id: Uuid,
7 }
8
9 /// One post's total endorsement count (from an aggregated `GROUP BY`).
10 #[derive(sqlx::FromRow)]
11 pub struct EndorsementCount {
12 pub post_id: Uuid,
13 pub count: i64,
14 }
15
16 /// Batch-fetch endorsements for a set of post IDs.
17 ///
18 /// Loads one row per endorsement, fine when the caller needs the endorser ids,
19 /// but the thread view must NOT use this: a hot post with thousands of
20 /// endorsements would stream every row on every (unauthenticated, unthrottled)
21 /// page load. That path uses [`count_endorsements_for_posts`] +
22 /// [`list_user_endorsed_posts`] instead.
23 #[tracing::instrument(skip_all)]
24 pub async fn list_endorsements_for_posts(
25 pool: &PgPool,
26 post_ids: &[Uuid],
27 ) -> Result<Vec<EndorsementRow>, sqlx::Error> {
28 sqlx::query_as!(
29 EndorsementRow,
30 "SELECT post_id, endorser_id FROM post_endorsements WHERE post_id = ANY($1)",
31 post_ids,
32 )
33 .fetch_all(pool)
34 .await
35 }
36
37 /// Per-post endorsement counts, aggregated in Postgres.
38 ///
39 /// Replaces streaming every endorsement row into the app just to `len()` them:
40 /// a `GROUP BY` returns at most one row per visible post regardless of how many
41 /// endorsements a post has (fuzz-2026-07-06 endorsement fan-out). Posts with
42 /// zero endorsements are absent; the caller defaults them to 0.
43 #[tracing::instrument(skip_all)]
44 pub async fn count_endorsements_for_posts(
45 pool: &PgPool,
46 post_ids: &[Uuid],
47 ) -> Result<Vec<EndorsementCount>, sqlx::Error> {
48 sqlx::query_as!(
49 EndorsementCount,
50 r#"SELECT post_id, COUNT(*) AS "count!"
51 FROM post_endorsements
52 WHERE post_id = ANY($1)
53 GROUP BY post_id"#,
54 post_ids,
55 )
56 .fetch_all(pool)
57 .await
58 }
59
60 /// The subset of `post_ids` the given user has endorsed. Bounded by the page's
61 /// post count (not the endorsement total), so the thread view can mark the
62 /// viewer's own endorsements without loading every endorser id.
63 #[tracing::instrument(skip_all)]
64 pub async fn list_user_endorsed_posts(
65 pool: &PgPool,
66 post_ids: &[Uuid],
67 user_id: Uuid,
68 ) -> Result<Vec<Uuid>, sqlx::Error> {
69 sqlx::query_scalar!(
70 "SELECT post_id FROM post_endorsements WHERE post_id = ANY($1) AND endorser_id = $2",
71 post_ids,
72 user_id,
73 )
74 .fetch_all(pool)
75 .await
76 }
77