use std::fmt; use anyhow::Result; use axum::{ extract::{Query, State}, response::IntoResponse, }; use axum_extra::extract::Cached; use axum_htmx::HxBoosted; use minijinja::context as template_context; use serde::{Deserialize, Serialize}; use crate::{ create_renderer, filtering::{EventFilterCriteria, EventSortField, FilteringService, FilterOptions, SortOrder}, http::{ context::WebContext, errors::WebError, event_view::{hydrate_event_organizers, hydrate_event_rsvp_counts, EventView}, middleware_auth::Auth, middleware_i18n::Language, pagination::Pagination, tab_selector::TabSelector, }, storage::event::event_list_recently_updated, }; #[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum HomeTab { UpcomingThisWeek, RecentlyUpdated, } impl fmt::Display for HomeTab { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HomeTab::UpcomingThisWeek => write!(f, "upcomingthisweek"), HomeTab::RecentlyUpdated => write!(f, "recentlyupdated"), } } } impl From for HomeTab { fn from(_: TabSelector) -> Self { HomeTab::UpcomingThisWeek } } /// Create filter criteria for upcoming events this week fn create_this_week_criteria() -> EventFilterCriteria { use chrono::{Utc, Weekday, Duration, Datelike}; let now = Utc::now(); // Calculate start of current week (Monday) let days_since_monday = match now.weekday() { Weekday::Mon => 0, Weekday::Tue => 1, Weekday::Wed => 2, Weekday::Thu => 3, Weekday::Fri => 4, Weekday::Sat => 5, Weekday::Sun => 6, }; let start_of_week = now - Duration::days(days_since_monday); let start_of_week = start_of_week .date_naive() .and_hms_opt(0, 0, 0) .unwrap() .and_utc(); // End of current week (Sunday 23:59:59) let end_of_week = start_of_week + Duration::days(6) + Duration::hours(23) + Duration::minutes(59) + Duration::seconds(59); EventFilterCriteria { start_date: Some(now), // Only events that haven't started yet end_date: Some(end_of_week), // Only events this week sort_by: EventSortField::StartTime, sort_order: SortOrder::Ascending, // Earliest events first page: 1, page_size: 10, // Limit to 10 events ..Default::default() } } pub async fn handle_index( State(web_context): State, HxBoosted(hx_boosted): HxBoosted, Language(language): Language, Cached(auth): Cached, pagination: Query, _tab_selector: Query, ) -> Result { // Create the template renderer with enhanced context let renderer = create_renderer!(web_context.clone(), Language(language.clone()), hx_boosted, false); let canonical_url = format!("https://{}/", web_context.config.external_base); let (_page, page_size) = pagination.clamped(); // Create filtering service for upcoming events let filtering_service = FilteringService::new(web_context.pool.clone()); // Get upcoming events this week let upcoming_events = { let criteria = create_this_week_criteria(); let options = FilterOptions { include_facets: false, include_hydration: true, hydration_options: crate::filtering::HydrationOptions { include_rsvp_counts: true, include_creator_handles: true, include_locations: false, max_events: Some(page_size as usize), // Limit to page_size events for homepage }, }; let locale_str = language.to_string(); match filtering_service.filter_events_with_locale(&criteria, &options, &locale_str).await { Ok(results) => { results.hydrated_events.into_iter() .map(|hydrated_event| crate::storage::event::model::EventWithRole { event: hydrated_event.event, role: "organizer".to_string(), }) .collect() } Err(err) => { tracing::warn!("Failed to fetch upcoming events: {}", err); Vec::new() } } }; // Get recently updated events (limited to page_size) let recently_updated_events = match event_list_recently_updated(&web_context.pool, 1, page_size).await { Ok(events) => events, Err(err) => { tracing::warn!("Failed to fetch recently updated events: {}", err); Vec::new() } }; // Process upcoming events let upcoming_organizer_handlers = hydrate_event_organizers(&web_context.pool, &upcoming_events).await?; let language_for_upcoming = language.clone(); let mut upcoming_event_views = upcoming_events .iter() .filter_map(|event_view| { let organizer_maybe = upcoming_organizer_handlers.get(&event_view.event.did); let event_view = EventView::try_from_with_locale( (auth.0.as_ref(), organizer_maybe, &event_view.event), Some(&language_for_upcoming) ); match event_view { Ok(event_view) => Some(event_view), Err(err) => { tracing::warn!(err = ?err, "error converting upcoming event view"); None } } }) .collect::>(); if let Err(err) = hydrate_event_rsvp_counts(&web_context.pool, &mut upcoming_event_views).await { tracing::warn!("Failed to hydrate upcoming event counts: {}", err); } // Process recently updated events let recent_organizer_handlers = hydrate_event_organizers(&web_context.pool, &recently_updated_events).await?; let language_for_recent = language.clone(); let mut recent_event_views = recently_updated_events .iter() .filter_map(|event_view| { let organizer_maybe = recent_organizer_handlers.get(&event_view.event.did); let event_view = EventView::try_from_with_locale( (auth.0.as_ref(), organizer_maybe, &event_view.event), Some(&language_for_recent) ); match event_view { Ok(event_view) => Some(event_view), Err(err) => { tracing::warn!(err = ?err, "error converting recent event view"); None } } }) .collect::>(); if let Err(err) = hydrate_event_rsvp_counts(&web_context.pool, &mut recent_event_views).await { tracing::warn!("Failed to hydrate recent event counts: {}", err); } // Limit both lists to page_size items using pagination logic let page_size_usize = page_size as usize; tracing::info!("Homepage pagination: page_size={}, upcoming_events.len()={}, recent_events.len()={}", page_size, upcoming_event_views.len(), recent_event_views.len()); if upcoming_event_views.len() > page_size_usize { upcoming_event_views.truncate(page_size_usize); tracing::info!("Truncated upcoming events to {} items", page_size_usize); } if recent_event_views.len() > page_size_usize { recent_event_views.truncate(page_size_usize); tracing::info!("Truncated recent events to {} items", page_size_usize); } Ok(( http::StatusCode::OK, renderer.render_template( "index", template_context! { upcoming_events => upcoming_event_views, events => recent_event_views, }, auth.0.as_ref(), &canonical_url, ), ) .into_response()) }