//! Feed page: items from followed users, projects, and tags. use crate::extractors::ValidatedQuery; use axum::extract::State; use axum::response::IntoResponse; use serde::Deserialize; use sqlx::PgPool; use tower_sessions::Session; use crate::{ auth::AuthUser, constants, db, error::Result, helpers::get_csrf_token, templates::FeedTemplate, types::DiscoverItem, }; /// Query parameters for the feed page. #[derive(Debug, Deserialize)] pub(super) struct FeedQuery { pub page: Option, } /// GET /feed: paginated feed of items from followed users, projects, and tags. #[tracing::instrument(skip_all, name = "feed::feed_page")] pub(super) async fn feed_page( State(db): State, session: Session, AuthUser(user): AuthUser, ValidatedQuery(query): ValidatedQuery, ) -> Result { let csrf_token = get_csrf_token(&session).await; let session_user = Some(user.clone()); // Clamp the upper bound too (matches admin/git pagination); the i64 widening // below already prevents the overflow panic, but an unbounded page is a // pointless huge offset (Run #2 UX MINOR). let page = query.page.unwrap_or(1).clamp(1, 1_000_000_000); // Widen to i64 BEFORE multiplying, `(page - 1) * FEED_PAGE_SIZE` in u32 // overflows for a large `?page=` (garbage offset in release, panic in debug). let offset = (page as i64 - 1) * constants::FEED_PAGE_SIZE as i64; let total_items = db::follows::count_followed_feed_items(&db, user.id).await? as u32; let total_pages = (total_items + constants::FEED_PAGE_SIZE - 1) / constants::FEED_PAGE_SIZE.max(1); let db_items = db::follows::get_followed_feed_items( &db, user.id, constants::FEED_PAGE_SIZE as i64, offset, ) .await?; let items: Vec = db_items.into_iter().map(DiscoverItem::from).collect(); // Compute the "showing X–Y" labels in i64 (saturating) to avoid the u32 // overflow `offset as u32 + FEED_PAGE_SIZE` would hit for a large `?page=`. let showing_start = if total_items == 0 { 0 } else { offset.saturating_add(1).clamp(0, u32::MAX as i64) as u32 }; let showing_end = offset .saturating_add(constants::FEED_PAGE_SIZE as i64) .min(total_items as i64) .clamp(0, u32::MAX as i64) as u32; let pagination_range = super::pagination::build_pagination_range(page, total_pages); Ok(FeedTemplate { csrf_token, session_user, items, total_items, current_page: page, total_pages, pagination_range, showing_start, showing_end, }) }