Skip to main content

max / makenotwork

2.6 KB · 79 lines History Blame Raw
1 //! Feed page: items from followed users, projects, and tags.
2
3 use crate::extractors::ValidatedQuery;
4 use axum::extract::State;
5 use axum::response::IntoResponse;
6 use serde::Deserialize;
7 use sqlx::PgPool;
8 use tower_sessions::Session;
9
10 use crate::{
11 auth::AuthUser, constants, db, error::Result, helpers::get_csrf_token, templates::FeedTemplate,
12 types::DiscoverItem,
13 };
14
15 /// Query parameters for the feed page.
16 #[derive(Debug, Deserialize)]
17 pub(super) struct FeedQuery {
18 pub page: Option<u32>,
19 }
20
21 /// GET /feed: paginated feed of items from followed users, projects, and tags.
22 #[tracing::instrument(skip_all, name = "feed::feed_page")]
23 pub(super) async fn feed_page(
24 State(db): State<PgPool>,
25 session: Session,
26 AuthUser(user): AuthUser,
27 ValidatedQuery(query): ValidatedQuery<FeedQuery>,
28 ) -> Result<impl IntoResponse> {
29 let csrf_token = get_csrf_token(&session).await;
30 let session_user = Some(user.clone());
31
32 // Clamp the upper bound too (matches admin/git pagination); the i64 widening
33 // below already prevents the overflow panic, but an unbounded page is a
34 // pointless huge offset (Run #2 UX MINOR).
35 let page = query.page.unwrap_or(1).clamp(1, 1_000_000_000);
36 // Widen to i64 BEFORE multiplying, `(page - 1) * FEED_PAGE_SIZE` in u32
37 // overflows for a large `?page=` (garbage offset in release, panic in debug).
38 let offset = (page as i64 - 1) * constants::FEED_PAGE_SIZE as i64;
39
40 let total_items = db::follows::count_followed_feed_items(&db, user.id).await? as u32;
41 let total_pages =
42 (total_items + constants::FEED_PAGE_SIZE - 1) / constants::FEED_PAGE_SIZE.max(1);
43
44 let db_items = db::follows::get_followed_feed_items(
45 &db,
46 user.id,
47 constants::FEED_PAGE_SIZE as i64,
48 offset,
49 )
50 .await?;
51
52 let items: Vec<DiscoverItem> = db_items.into_iter().map(DiscoverItem::from).collect();
53
54 // Compute the "showing X–Y" labels in i64 (saturating) to avoid the u32
55 // overflow `offset as u32 + FEED_PAGE_SIZE` would hit for a large `?page=`.
56 let showing_start = if total_items == 0 {
57 0
58 } else {
59 offset.saturating_add(1).clamp(0, u32::MAX as i64) as u32
60 };
61 let showing_end = offset
62 .saturating_add(constants::FEED_PAGE_SIZE as i64)
63 .min(total_items as i64)
64 .clamp(0, u32::MAX as i64) as u32;
65 let pagination_range = super::pagination::build_pagination_range(page, total_pages);
66
67 Ok(FeedTemplate {
68 csrf_token,
69 session_user,
70 items,
71 total_items,
72 current_page: page,
73 total_pages,
74 pagination_range,
75 showing_start,
76 showing_end,
77 })
78 }
79