Skip to main content

max / makenotwork

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