forked from
smokesignal.events/smokesignal
i18n+filtering fork - fluent-templates v2
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}