Fork i18n + search + filtering- v0.2

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#

  1. Basic Usage Patterns
  2. Fixed Query Templates
  3. Integration Examples
  4. API Reference
  5. Template Usage
  6. Performance Optimization
  7. Error Handling

Basic Usage Patterns#

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_URL configured
  • Redis connection available (for caching)
  • Handlebars templates created for your use case
  • Localization strings added to i18n/*/ui.ftl files
  • Error handling implemented for your specific needs

Template Implementation Steps#

  1. Define your fixed criteria in a template struct
  2. Implement the query method using EventFilterCriteria
  3. Create the route handler in your Axum router
  4. Add the Handlebars template for rendering
  5. Add localization strings for user-facing text
  6. Implement caching for frequently-used templates
  7. Add error handling and fallback behavior
  8. 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