i18n+filtering fork - fluent-templates v2
at main 227 lines 8.0 kB view raw
1use std::fmt; 2 3use anyhow::Result; 4use axum::{ 5 extract::{Query, State}, 6 response::IntoResponse, 7}; 8use axum_extra::extract::Cached; 9use axum_htmx::HxBoosted; 10 11use minijinja::context as template_context; 12use serde::{Deserialize, Serialize}; 13 14use crate::{ 15 create_renderer, 16 filtering::{EventFilterCriteria, EventSortField, FilteringService, FilterOptions, SortOrder}, 17 http::{ 18 context::WebContext, 19 errors::WebError, 20 event_view::{hydrate_event_organizers, hydrate_event_rsvp_counts, EventView}, 21 middleware_auth::Auth, 22 middleware_i18n::Language, 23 pagination::Pagination, 24 tab_selector::TabSelector, 25 }, 26 storage::event::event_list_recently_updated, 27}; 28 29#[derive(Debug, Deserialize, Serialize, PartialEq)] 30pub enum HomeTab { 31 UpcomingThisWeek, 32 RecentlyUpdated, 33} 34 35impl fmt::Display for HomeTab { 36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 37 match self { 38 HomeTab::UpcomingThisWeek => write!(f, "upcomingthisweek"), 39 HomeTab::RecentlyUpdated => write!(f, "recentlyupdated"), 40 } 41 } 42} 43 44impl From<TabSelector> for HomeTab { 45 fn from(_: TabSelector) -> Self { 46 HomeTab::UpcomingThisWeek 47 } 48} 49 50/// Create filter criteria for upcoming events this week 51fn create_this_week_criteria() -> EventFilterCriteria { 52 use chrono::{Utc, Weekday, Duration, Datelike}; 53 54 let now = Utc::now(); 55 56 // Calculate start of current week (Monday) 57 let days_since_monday = match now.weekday() { 58 Weekday::Mon => 0, 59 Weekday::Tue => 1, 60 Weekday::Wed => 2, 61 Weekday::Thu => 3, 62 Weekday::Fri => 4, 63 Weekday::Sat => 5, 64 Weekday::Sun => 6, 65 }; 66 67 let start_of_week = now - Duration::days(days_since_monday); 68 let start_of_week = start_of_week 69 .date_naive() 70 .and_hms_opt(0, 0, 0) 71 .unwrap() 72 .and_utc(); 73 74 // End of current week (Sunday 23:59:59) 75 let end_of_week = start_of_week + Duration::days(6) + Duration::hours(23) + Duration::minutes(59) + Duration::seconds(59); 76 77 EventFilterCriteria { 78 start_date: Some(now), // Only events that haven't started yet 79 end_date: Some(end_of_week), // Only events this week 80 sort_by: EventSortField::StartTime, 81 sort_order: SortOrder::Ascending, // Earliest events first 82 page: 1, 83 page_size: 10, // Limit to 10 events 84 ..Default::default() 85 } 86} 87 88pub async fn handle_index( 89 State(web_context): State<WebContext>, 90 HxBoosted(hx_boosted): HxBoosted, 91 Language(language): Language, 92 Cached(auth): Cached<Auth>, 93 pagination: Query<Pagination>, 94 _tab_selector: Query<TabSelector>, 95) -> Result<impl IntoResponse, WebError> { 96 // Create the template renderer with enhanced context 97 let renderer = create_renderer!(web_context.clone(), Language(language.clone()), hx_boosted, false); 98 99 let canonical_url = format!("https://{}/", web_context.config.external_base); 100 101 let (_page, page_size) = pagination.clamped(); 102 103 // Create filtering service for upcoming events 104 let filtering_service = FilteringService::new(web_context.pool.clone()); 105 106 // Get upcoming events this week 107 let upcoming_events = { 108 let criteria = create_this_week_criteria(); 109 let options = FilterOptions { 110 include_facets: false, 111 include_hydration: true, 112 hydration_options: crate::filtering::HydrationOptions { 113 include_rsvp_counts: true, 114 include_creator_handles: true, 115 include_locations: false, 116 max_events: Some(page_size as usize), // Limit to page_size events for homepage 117 }, 118 }; 119 120 let locale_str = language.to_string(); 121 122 match filtering_service.filter_events_with_locale(&criteria, &options, &locale_str).await { 123 Ok(results) => { 124 results.hydrated_events.into_iter() 125 .map(|hydrated_event| crate::storage::event::model::EventWithRole { 126 event: hydrated_event.event, 127 role: "organizer".to_string(), 128 }) 129 .collect() 130 } 131 Err(err) => { 132 tracing::warn!("Failed to fetch upcoming events: {}", err); 133 Vec::new() 134 } 135 } 136 }; 137 138 // Get recently updated events (limited to page_size) 139 let recently_updated_events = match event_list_recently_updated(&web_context.pool, 1, page_size).await { 140 Ok(events) => events, 141 Err(err) => { 142 tracing::warn!("Failed to fetch recently updated events: {}", err); 143 Vec::new() 144 } 145 }; 146 147 // Process upcoming events 148 let upcoming_organizer_handlers = hydrate_event_organizers(&web_context.pool, &upcoming_events).await?; 149 let language_for_upcoming = language.clone(); 150 let mut upcoming_event_views = upcoming_events 151 .iter() 152 .filter_map(|event_view| { 153 let organizer_maybe = upcoming_organizer_handlers.get(&event_view.event.did); 154 let event_view = EventView::try_from_with_locale( 155 (auth.0.as_ref(), organizer_maybe, &event_view.event), 156 Some(&language_for_upcoming) 157 ); 158 159 match event_view { 160 Ok(event_view) => Some(event_view), 161 Err(err) => { 162 tracing::warn!(err = ?err, "error converting upcoming event view"); 163 None 164 } 165 } 166 }) 167 .collect::<Vec<EventView>>(); 168 169 if let Err(err) = hydrate_event_rsvp_counts(&web_context.pool, &mut upcoming_event_views).await { 170 tracing::warn!("Failed to hydrate upcoming event counts: {}", err); 171 } 172 173 // Process recently updated events 174 let recent_organizer_handlers = hydrate_event_organizers(&web_context.pool, &recently_updated_events).await?; 175 let language_for_recent = language.clone(); 176 let mut recent_event_views = recently_updated_events 177 .iter() 178 .filter_map(|event_view| { 179 let organizer_maybe = recent_organizer_handlers.get(&event_view.event.did); 180 let event_view = EventView::try_from_with_locale( 181 (auth.0.as_ref(), organizer_maybe, &event_view.event), 182 Some(&language_for_recent) 183 ); 184 185 match event_view { 186 Ok(event_view) => Some(event_view), 187 Err(err) => { 188 tracing::warn!(err = ?err, "error converting recent event view"); 189 None 190 } 191 } 192 }) 193 .collect::<Vec<EventView>>(); 194 195 if let Err(err) = hydrate_event_rsvp_counts(&web_context.pool, &mut recent_event_views).await { 196 tracing::warn!("Failed to hydrate recent event counts: {}", err); 197 } 198 199 // Limit both lists to page_size items using pagination logic 200 let page_size_usize = page_size as usize; 201 tracing::info!("Homepage pagination: page_size={}, upcoming_events.len()={}, recent_events.len()={}", 202 page_size, upcoming_event_views.len(), recent_event_views.len()); 203 204 if upcoming_event_views.len() > page_size_usize { 205 upcoming_event_views.truncate(page_size_usize); 206 tracing::info!("Truncated upcoming events to {} items", page_size_usize); 207 } 208 209 if recent_event_views.len() > page_size_usize { 210 recent_event_views.truncate(page_size_usize); 211 tracing::info!("Truncated recent events to {} items", page_size_usize); 212 } 213 214 Ok(( 215 http::StatusCode::OK, 216 renderer.render_template( 217 "index", 218 template_context! { 219 upcoming_events => upcoming_event_views, 220 events => recent_event_views, 221 }, 222 auth.0.as_ref(), 223 &canonical_url, 224 ), 225 ) 226 .into_response()) 227}