Event Filtering System - Usage and Integration Guide#
Overview#
This document provides practical examples and integration patterns for using the event filtering system in the smokesignal-eTD application. It demonstrates how to implement filtering with both dynamic user inputs and fixed query templates for specific use cases.
Table of Contents#
- Basic Usage Patterns
- Fixed Query Templates
- Integration Examples
- API Reference
- Template Usage
- Performance Optimization
- Error Handling
Basic Usage Patterns#
1. Simple Text Search#
use crate::filtering::{EventFilterCriteria, FilteringService};
// Basic text search for "conference" events
let criteria = EventFilterCriteria {
search_text: Some("conference".to_string()),
..Default::default()
};
let service = FilteringService::new(pool.clone());
let results = service.filter_events(&criteria, 1, 20).await?;
2. Date Range Filtering#
use chrono::{DateTime, Utc};
let criteria = EventFilterCriteria {
date_from: Some(DateTime::parse_from_rfc3339("2025-06-01T00:00:00Z")?.with_timezone(&Utc)),
date_to: Some(DateTime::parse_from_rfc3339("2025-12-31T23:59:59Z")?.with_timezone(&Utc)),
..Default::default()
};
let results = service.filter_events(&criteria, 1, 50).await?;
3. Location-Based Filtering#
let criteria = EventFilterCriteria {
location_text: Some("Montreal".to_string()),
location_radius_km: Some(25.0),
..Default::default()
};
let results = service.filter_events(&criteria, 1, 30).await?;
Fixed Query Templates#
Template 1: Upcoming Tech Events#
Perfect for embedding in tech-focused pages or newsletters.
use crate::filtering::{EventFilterCriteria, FilteringService};
use chrono::{DateTime, Utc, Duration};
pub struct TechEventsTemplate;
impl TechEventsTemplate {
/// Get upcoming tech events in the next 30 days
pub async fn get_upcoming_tech_events(
service: &FilteringService,
location: Option<String>,
) -> Result<FilteredEventsResult, FilteringError> {
let now = Utc::now();
let thirty_days = now + Duration::days(30);
let criteria = EventFilterCriteria {
// Tech-related keywords
search_text: Some("technology OR programming OR developer OR startup OR AI OR software OR web OR mobile OR data".to_string()),
// Only future events
date_from: Some(now),
date_to: Some(thirty_days),
// Optional location filter
location_text: location,
location_radius_km: Some(50.0),
// Sort by date ascending (soonest first)
sort_by: Some("date_asc".to_string()),
..Default::default()
};
// Get first 10 results
service.filter_events(&criteria, 1, 10).await
}
}
// Usage example
let tech_events = TechEventsTemplate::get_upcoming_tech_events(
&service,
Some("San Francisco".to_string())
).await?;
Template 2: Weekend Community Events#
Ideal for community pages or local event discovery.
pub struct CommunityEventsTemplate;
impl CommunityEventsTemplate {
/// Get community events happening this weekend
pub async fn get_weekend_community_events(
service: &FilteringService,
city: &str,
) -> Result<FilteredEventsResult, FilteringError> {
let now = Utc::now();
let days_until_saturday = (6 - now.weekday().num_days_from_monday()) % 7;
let saturday = now + Duration::days(days_until_saturday as i64);
let sunday = saturday + Duration::days(1);
let criteria = EventFilterCriteria {
// Community-focused keywords
search_text: Some("community OR meetup OR networking OR social OR volunteer OR local OR neighborhood".to_string()),
// Weekend timeframe
date_from: Some(saturday),
date_to: Some(sunday + Duration::hours(23) + Duration::minutes(59)),
// Specific city
location_text: Some(city.to_string()),
location_radius_km: Some(25.0),
// Sort by popularity (most RSVPs first)
sort_by: Some("popularity_desc".to_string()),
..Default::default()
};
service.filter_events(&criteria, 1, 15).await
}
}
// Usage example
let weekend_events = CommunityEventsTemplate::get_weekend_community_events(
&service,
"Toronto"
).await?;
Template 3: Free Educational Events#
Great for student portals or educational institutions.
pub struct EducationalEventsTemplate;
impl EducationalEventsTemplate {
/// Get free educational events in the next 60 days
pub async fn get_free_educational_events(
service: &FilteringService,
subject_area: Option<String>,
) -> Result<FilteredEventsResult, FilteringError> {
let now = Utc::now();
let sixty_days = now + Duration::days(60);
let mut search_terms = vec![
"workshop", "seminar", "lecture", "course", "tutorial",
"training", "learning", "education", "free", "no cost"
];
// Add subject-specific terms if provided
if let Some(subject) = &subject_area {
search_terms.push(subject);
}
let criteria = EventFilterCriteria {
search_text: Some(search_terms.join(" OR ")),
// Next 60 days
date_from: Some(now),
date_to: Some(sixty_days),
// Filter for likely free events
// This could be enhanced with a dedicated "free" field
// Sort by date ascending
sort_by: Some("date_asc".to_string()),
..Default::default()
};
service.filter_events(&criteria, 1, 20).await
}
}
// Usage examples
let programming_workshops = EducationalEventsTemplate::get_free_educational_events(
&service,
Some("programming".to_string())
).await?;
let general_education = EducationalEventsTemplate::get_free_educational_events(
&service,
None
).await?;
Template 4: Tonight's Events#
Perfect for "what's happening tonight" widgets.
pub struct TonightEventsTemplate;
impl TonightEventsTemplate {
/// Get events happening tonight in a specific area
pub async fn get_tonights_events(
service: &FilteringService,
location: &str,
radius_km: f64,
) -> Result<FilteredEventsResult, FilteringError> {
let now = Utc::now();
let tonight_start = now.date_naive().and_hms_opt(18, 0, 0)
.unwrap().and_local_timezone(Utc).unwrap();
let tonight_end = now.date_naive().and_hms_opt(23, 59, 59)
.unwrap().and_local_timezone(Utc).unwrap();
let criteria = EventFilterCriteria {
// Evening/night events
date_from: Some(tonight_start),
date_to: Some(tonight_end),
// Location constraint
location_text: Some(location.to_string()),
location_radius_km: Some(radius_km),
// Sort by start time
sort_by: Some("date_asc".to_string()),
..Default::default()
};
service.filter_events(&criteria, 1, 10).await
}
}
// Usage example
let tonight = TonightEventsTemplate::get_tonights_events(
&service,
"Vancouver",
15.0
).await?;
Integration Examples#
1. Axum Route Handler with Fixed Template#
use axum::{extract::State, response::Html, Extension};
use crate::http::context::WebContext;
use crate::filtering::FilteringService;
pub async fn handle_tech_events_page(
State(context): State<WebContext>,
Extension(user_location): Extension<Option<String>>,
) -> Result<Html<String>, AppError> {
let service = FilteringService::new(context.storage_pool.clone());
// Use the fixed template
let events = TechEventsTemplate::get_upcoming_tech_events(
&service,
user_location
).await?;
// Render template
let rendered = context.handlebars.render("tech_events_page", &json!({
"events": events.events,
"facets": events.facets,
"total_count": events.total_count,
"page_title": "Upcoming Tech Events"
}))?;
Ok(Html(rendered))
}
2. HTMX Widget for Dashboard#
pub async fn handle_weekend_events_widget(
State(context): State<WebContext>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Html<String>, AppError> {
let city = params.get("city").cloned()
.unwrap_or_else(|| "Montreal".to_string());
let service = FilteringService::new(context.storage_pool.clone());
let events = CommunityEventsTemplate::get_weekend_community_events(
&service,
&city
).await?;
// Render as HTMX partial
let rendered = context.handlebars.render("weekend_events_widget", &json!({
"events": events.events,
"city": city
}))?;
Ok(Html(rendered))
}
3. API Endpoint for Mobile App#
use axum::Json;
use serde_json::json;
pub async fn api_tonight_events(
State(context): State<WebContext>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Json<Value>, AppError> {
let location = params.get("location")
.ok_or_else(|| AppError::BadRequest("location parameter required".to_string()))?;
let radius = params.get("radius")
.and_then(|r| r.parse::<f64>().ok())
.unwrap_or(10.0);
let service = FilteringService::new(context.storage_pool.clone());
let events = TonightEventsTemplate::get_tonights_events(
&service,
location,
radius
).await?;
Ok(Json(json!({
"success": true,
"data": {
"events": events.events,
"total_count": events.total_count,
"location": location,
"radius_km": radius
}
})))
}
API Reference#
FilteringService Methods#
impl FilteringService {
/// Create a new filtering service instance
pub fn new(pool: sqlx::PgPool) -> Self;
/// Filter events with full criteria support
pub async fn filter_events(
&self,
criteria: &EventFilterCriteria,
page: i64,
page_size: i64,
) -> Result<FilteredEventsResult, FilteringError>;
/// Get facet counts for refining filters
pub async fn calculate_facets(
&self,
criteria: &EventFilterCriteria,
) -> Result<EventFacets, FilteringError>;
/// Hydrate events with additional data
pub async fn hydrate_events(
&self,
events: &mut [Event],
strategy: HydrationStrategy,
) -> Result<(), FilteringError>;
}
EventFilterCriteria Fields#
pub struct EventFilterCriteria {
/// Text search across event content
pub search_text: Option<String>,
/// Filter by date range
pub date_from: Option<DateTime<Utc>>,
pub date_to: Option<DateTime<Utc>>,
/// Location-based filtering
pub location_text: Option<String>,
pub location_latitude: Option<f64>,
pub location_longitude: Option<f64>,
pub location_radius_km: Option<f64>,
/// Event type filtering
pub event_types: Option<Vec<String>>,
/// Organizer filtering
pub organizer_handles: Option<Vec<String>>,
/// Sorting options
pub sort_by: Option<String>, // "date_asc", "date_desc", "popularity_desc", "relevance"
/// Language filtering
pub languages: Option<Vec<String>>,
}
Template Usage#
1. Tech Events Page Template#
{{!-- templates/tech_events_page.en-us.html --}}
<div class="tech-events-page">
<h1>{{tr "tech-events-title"}}</h1>
<p class="subtitle">{{tr "tech-events-subtitle"}}</p>
<div class="events-grid">
{{#each events}}
<div class="event-card">
<h3><a href="/{{organizer_handle}}/{{rkey}}">{{title}}</a></h3>
<p class="event-date">{{format_date start_time}}</p>
<p class="event-location">{{location.name}}</p>
<p class="event-description">{{truncate description 150}}</p>
</div>
{{/each}}
</div>
{{#if (gt total_count events.length)}}
<p class="more-events">
<a href="/events?search=technology OR programming OR developer">
{{tr "view-all-tech-events"}}
</a>
</p>
{{/if}}
</div>
2. Weekend Events Widget#
{{!-- templates/weekend_events_widget.en-us.incl.html --}}
<div class="weekend-widget"
hx-get="/api/weekend-events?city={{city}}"
hx-trigger="every 30m">
<h4>{{tr "this-weekend-in"}} {{city}}</h4>
{{#if events}}
<ul class="event-list">
{{#each events}}
<li class="event-item">
<a href="/{{organizer_handle}}/{{rkey}}">
<strong>{{title}}</strong>
<span class="event-time">{{format_time start_time}}</span>
</a>
</li>
{{/each}}
</ul>
{{else}}
<p class="no-events">{{tr "no-weekend-events"}}</p>
{{/if}}
</div>
3. Tonight's Events Notification#
{{!-- templates/tonight_events_notification.en-us.incl.html --}}
{{#if events}}
<div class="notification is-info">
<button class="delete" onclick="this.parentElement.style.display='none'"></button>
<strong>{{tr "happening-tonight"}}:</strong>
{{#each events}}
<a href="/{{organizer_handle}}/{{rkey}}">{{title}}</a>{{#unless @last}}, {{/unless}}
{{/each}}
</div>
{{/if}}
Performance Optimization#
1. Caching Fixed Templates#
use redis::AsyncCommands;
impl TechEventsTemplate {
pub async fn get_cached_tech_events(
service: &FilteringService,
redis: &mut redis::aio::Connection,
location: Option<String>,
) -> Result<FilteredEventsResult, FilteringError> {
let cache_key = format!("tech_events:{}",
location.as_deref().unwrap_or("global"));
// Try cache first
if let Ok(cached) = redis.get::<_, String>(&cache_key).await {
if let Ok(events) = serde_json::from_str(&cached) {
return Ok(events);
}
}
// Fallback to database
let events = Self::get_upcoming_tech_events(service, location).await?;
// Cache for 15 minutes
let serialized = serde_json::to_string(&events)?;
let _: () = redis.setex(&cache_key, 900, serialized).await?;
Ok(events)
}
}
2. Background Updates#
use tokio::time::{interval, Duration};
pub async fn start_template_cache_updater(
service: FilteringService,
redis_pool: redis::aio::ConnectionManager,
) {
let mut interval = interval(Duration::from_secs(600)); // 10 minutes
loop {
interval.tick().await;
// Update popular templates
let cities = vec!["Montreal", "Toronto", "Vancouver", "Calgary"];
for city in cities {
if let Ok(mut conn) = redis_pool.clone().into_connection().await {
let _ = TechEventsTemplate::get_cached_tech_events(
&service,
&mut conn,
Some(city.to_string())
).await;
let _ = CommunityEventsTemplate::get_weekend_community_events(
&service,
city
).await;
}
}
}
}
Error Handling#
1. Graceful Degradation#
pub async fn handle_tech_events_safe(
service: &FilteringService,
location: Option<String>,
) -> FilteredEventsResult {
match TechEventsTemplate::get_upcoming_tech_events(service, location).await {
Ok(events) => events,
Err(err) => {
tracing::error!("Failed to fetch tech events: {}", err);
// Return empty result with error indication
FilteredEventsResult {
events: vec![],
facets: EventFacets::default(),
total_count: 0,
has_more: false,
error_message: Some("Unable to load events at this time".to_string()),
}
}
}
}
2. Fallback Templates#
impl TechEventsTemplate {
pub async fn get_tech_events_with_fallback(
service: &FilteringService,
location: Option<String>,
) -> Result<FilteredEventsResult, FilteringError> {
// Try specific tech events first
if let Ok(events) = Self::get_upcoming_tech_events(service, location.clone()).await {
if !events.events.is_empty() {
return Ok(events);
}
}
// Fallback to broader search
let criteria = EventFilterCriteria {
search_text: Some("event OR meetup OR conference".to_string()),
date_from: Some(Utc::now()),
date_to: Some(Utc::now() + Duration::days(30)),
location_text: location,
location_radius_km: Some(50.0),
sort_by: Some("date_asc".to_string()),
..Default::default()
};
service.filter_events(&criteria, 1, 10).await
}
}
Integration Checklist#
Before Using Fixed Templates#
- Database migrations applied (
20250530104334_event_filtering_indexes.sql) - Environment variable
DATABASE_URLconfigured - Redis connection available (for caching)
- Handlebars templates created for your use case
- Localization strings added to
i18n/*/ui.ftlfiles - Error handling implemented for your specific needs
Template Implementation Steps#
- Define your fixed criteria in a template struct
- Implement the query method using
EventFilterCriteria - Create the route handler in your Axum router
- Add the Handlebars template for rendering
- Add localization strings for user-facing text
- Implement caching for frequently-used templates
- Add error handling and fallback behavior
- Test with realistic data to verify performance
Performance Considerations#
- Cache frequently-used templates (tech events, weekend events)
- Use background jobs to pre-populate cache
- Implement fallback queries for when specific searches return no results
- Monitor query performance and adjust indexes as needed
- Consider pagination for templates that might return many results
This guide provides a complete reference for integrating the event filtering system with fixed query templates. The examples demonstrate real-world usage patterns that can be adapted for specific application needs while maintaining performance and user experience.
Generated: May 30, 2025
Author: GitHub Copilot
Version: Usage Guide v1.0