+649
docs/filter_module_USAGE.md
+649
docs/filter_module_USAGE.md
···
1
+
# Event Filtering System - Usage and Integration Guide
2
+
3
+
## Overview
4
+
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.
5
+
6
+
## Table of Contents
7
+
1. [Basic Usage Patterns](#basic-usage-patterns)
8
+
2. [Fixed Query Templates](#fixed-query-templates)
9
+
3. [Integration Examples](#integration-examples)
10
+
4. [API Reference](#api-reference)
11
+
5. [Template Usage](#template-usage)
12
+
6. [Performance Optimization](#performance-optimization)
13
+
7. [Error Handling](#error-handling)
14
+
15
+
## Basic Usage Patterns
16
+
17
+
### 1. Simple Text Search
18
+
```rust
19
+
use crate::filtering::{EventFilterCriteria, FilteringService};
20
+
21
+
// Basic text search for "conference" events
22
+
let criteria = EventFilterCriteria {
23
+
search_text: Some("conference".to_string()),
24
+
..Default::default()
25
+
};
26
+
27
+
let service = FilteringService::new(pool.clone());
28
+
let results = service.filter_events(&criteria, 1, 20).await?;
29
+
```
30
+
31
+
### 2. Date Range Filtering
32
+
```rust
33
+
use chrono::{DateTime, Utc};
34
+
35
+
let criteria = EventFilterCriteria {
36
+
date_from: Some(DateTime::parse_from_rfc3339("2025-06-01T00:00:00Z")?.with_timezone(&Utc)),
37
+
date_to: Some(DateTime::parse_from_rfc3339("2025-12-31T23:59:59Z")?.with_timezone(&Utc)),
38
+
..Default::default()
39
+
};
40
+
41
+
let results = service.filter_events(&criteria, 1, 50).await?;
42
+
```
43
+
44
+
### 3. Location-Based Filtering
45
+
```rust
46
+
let criteria = EventFilterCriteria {
47
+
location_text: Some("Montreal".to_string()),
48
+
location_radius_km: Some(25.0),
49
+
..Default::default()
50
+
};
51
+
52
+
let results = service.filter_events(&criteria, 1, 30).await?;
53
+
```
54
+
55
+
## Fixed Query Templates
56
+
57
+
### Template 1: Upcoming Tech Events
58
+
Perfect for embedding in tech-focused pages or newsletters.
59
+
60
+
```rust
61
+
use crate::filtering::{EventFilterCriteria, FilteringService};
62
+
use chrono::{DateTime, Utc, Duration};
63
+
64
+
pub struct TechEventsTemplate;
65
+
66
+
impl TechEventsTemplate {
67
+
/// Get upcoming tech events in the next 30 days
68
+
pub async fn get_upcoming_tech_events(
69
+
service: &FilteringService,
70
+
location: Option<String>,
71
+
) -> Result<FilteredEventsResult, FilteringError> {
72
+
let now = Utc::now();
73
+
let thirty_days = now + Duration::days(30);
74
+
75
+
let criteria = EventFilterCriteria {
76
+
// Tech-related keywords
77
+
search_text: Some("technology OR programming OR developer OR startup OR AI OR software OR web OR mobile OR data".to_string()),
78
+
79
+
// Only future events
80
+
date_from: Some(now),
81
+
date_to: Some(thirty_days),
82
+
83
+
// Optional location filter
84
+
location_text: location,
85
+
location_radius_km: Some(50.0),
86
+
87
+
// Sort by date ascending (soonest first)
88
+
sort_by: Some("date_asc".to_string()),
89
+
90
+
..Default::default()
91
+
};
92
+
93
+
// Get first 10 results
94
+
service.filter_events(&criteria, 1, 10).await
95
+
}
96
+
}
97
+
98
+
// Usage example
99
+
let tech_events = TechEventsTemplate::get_upcoming_tech_events(
100
+
&service,
101
+
Some("San Francisco".to_string())
102
+
).await?;
103
+
```
104
+
105
+
### Template 2: Weekend Community Events
106
+
Ideal for community pages or local event discovery.
107
+
108
+
```rust
109
+
pub struct CommunityEventsTemplate;
110
+
111
+
impl CommunityEventsTemplate {
112
+
/// Get community events happening this weekend
113
+
pub async fn get_weekend_community_events(
114
+
service: &FilteringService,
115
+
city: &str,
116
+
) -> Result<FilteredEventsResult, FilteringError> {
117
+
let now = Utc::now();
118
+
let days_until_saturday = (6 - now.weekday().num_days_from_monday()) % 7;
119
+
let saturday = now + Duration::days(days_until_saturday as i64);
120
+
let sunday = saturday + Duration::days(1);
121
+
122
+
let criteria = EventFilterCriteria {
123
+
// Community-focused keywords
124
+
search_text: Some("community OR meetup OR networking OR social OR volunteer OR local OR neighborhood".to_string()),
125
+
126
+
// Weekend timeframe
127
+
date_from: Some(saturday),
128
+
date_to: Some(sunday + Duration::hours(23) + Duration::minutes(59)),
129
+
130
+
// Specific city
131
+
location_text: Some(city.to_string()),
132
+
location_radius_km: Some(25.0),
133
+
134
+
// Sort by popularity (most RSVPs first)
135
+
sort_by: Some("popularity_desc".to_string()),
136
+
137
+
..Default::default()
138
+
};
139
+
140
+
service.filter_events(&criteria, 1, 15).await
141
+
}
142
+
}
143
+
144
+
// Usage example
145
+
let weekend_events = CommunityEventsTemplate::get_weekend_community_events(
146
+
&service,
147
+
"Toronto"
148
+
).await?;
149
+
```
150
+
151
+
### Template 3: Free Educational Events
152
+
Great for student portals or educational institutions.
153
+
154
+
```rust
155
+
pub struct EducationalEventsTemplate;
156
+
157
+
impl EducationalEventsTemplate {
158
+
/// Get free educational events in the next 60 days
159
+
pub async fn get_free_educational_events(
160
+
service: &FilteringService,
161
+
subject_area: Option<String>,
162
+
) -> Result<FilteredEventsResult, FilteringError> {
163
+
let now = Utc::now();
164
+
let sixty_days = now + Duration::days(60);
165
+
166
+
let mut search_terms = vec![
167
+
"workshop", "seminar", "lecture", "course", "tutorial",
168
+
"training", "learning", "education", "free", "no cost"
169
+
];
170
+
171
+
// Add subject-specific terms if provided
172
+
if let Some(subject) = &subject_area {
173
+
search_terms.push(subject);
174
+
}
175
+
176
+
let criteria = EventFilterCriteria {
177
+
search_text: Some(search_terms.join(" OR ")),
178
+
179
+
// Next 60 days
180
+
date_from: Some(now),
181
+
date_to: Some(sixty_days),
182
+
183
+
// Filter for likely free events
184
+
// This could be enhanced with a dedicated "free" field
185
+
186
+
// Sort by date ascending
187
+
sort_by: Some("date_asc".to_string()),
188
+
189
+
..Default::default()
190
+
};
191
+
192
+
service.filter_events(&criteria, 1, 20).await
193
+
}
194
+
}
195
+
196
+
// Usage examples
197
+
let programming_workshops = EducationalEventsTemplate::get_free_educational_events(
198
+
&service,
199
+
Some("programming".to_string())
200
+
).await?;
201
+
202
+
let general_education = EducationalEventsTemplate::get_free_educational_events(
203
+
&service,
204
+
None
205
+
).await?;
206
+
```
207
+
208
+
### Template 4: Tonight's Events
209
+
Perfect for "what's happening tonight" widgets.
210
+
211
+
```rust
212
+
pub struct TonightEventsTemplate;
213
+
214
+
impl TonightEventsTemplate {
215
+
/// Get events happening tonight in a specific area
216
+
pub async fn get_tonights_events(
217
+
service: &FilteringService,
218
+
location: &str,
219
+
radius_km: f64,
220
+
) -> Result<FilteredEventsResult, FilteringError> {
221
+
let now = Utc::now();
222
+
let tonight_start = now.date_naive().and_hms_opt(18, 0, 0)
223
+
.unwrap().and_local_timezone(Utc).unwrap();
224
+
let tonight_end = now.date_naive().and_hms_opt(23, 59, 59)
225
+
.unwrap().and_local_timezone(Utc).unwrap();
226
+
227
+
let criteria = EventFilterCriteria {
228
+
// Evening/night events
229
+
date_from: Some(tonight_start),
230
+
date_to: Some(tonight_end),
231
+
232
+
// Location constraint
233
+
location_text: Some(location.to_string()),
234
+
location_radius_km: Some(radius_km),
235
+
236
+
// Sort by start time
237
+
sort_by: Some("date_asc".to_string()),
238
+
239
+
..Default::default()
240
+
};
241
+
242
+
service.filter_events(&criteria, 1, 10).await
243
+
}
244
+
}
245
+
246
+
// Usage example
247
+
let tonight = TonightEventsTemplate::get_tonights_events(
248
+
&service,
249
+
"Vancouver",
250
+
15.0
251
+
).await?;
252
+
```
253
+
254
+
## Integration Examples
255
+
256
+
### 1. Axum Route Handler with Fixed Template
257
+
258
+
```rust
259
+
use axum::{extract::State, response::Html, Extension};
260
+
use crate::http::context::WebContext;
261
+
use crate::filtering::FilteringService;
262
+
263
+
pub async fn handle_tech_events_page(
264
+
State(context): State<WebContext>,
265
+
Extension(user_location): Extension<Option<String>>,
266
+
) -> Result<Html<String>, AppError> {
267
+
let service = FilteringService::new(context.storage_pool.clone());
268
+
269
+
// Use the fixed template
270
+
let events = TechEventsTemplate::get_upcoming_tech_events(
271
+
&service,
272
+
user_location
273
+
).await?;
274
+
275
+
// Render template
276
+
let rendered = context.handlebars.render("tech_events_page", &json!({
277
+
"events": events.events,
278
+
"facets": events.facets,
279
+
"total_count": events.total_count,
280
+
"page_title": "Upcoming Tech Events"
281
+
}))?;
282
+
283
+
Ok(Html(rendered))
284
+
}
285
+
```
286
+
287
+
### 2. HTMX Widget for Dashboard
288
+
289
+
```rust
290
+
pub async fn handle_weekend_events_widget(
291
+
State(context): State<WebContext>,
292
+
Query(params): Query<HashMap<String, String>>,
293
+
) -> Result<Html<String>, AppError> {
294
+
let city = params.get("city").cloned()
295
+
.unwrap_or_else(|| "Montreal".to_string());
296
+
297
+
let service = FilteringService::new(context.storage_pool.clone());
298
+
let events = CommunityEventsTemplate::get_weekend_community_events(
299
+
&service,
300
+
&city
301
+
).await?;
302
+
303
+
// Render as HTMX partial
304
+
let rendered = context.handlebars.render("weekend_events_widget", &json!({
305
+
"events": events.events,
306
+
"city": city
307
+
}))?;
308
+
309
+
Ok(Html(rendered))
310
+
}
311
+
```
312
+
313
+
### 3. API Endpoint for Mobile App
314
+
315
+
```rust
316
+
use axum::Json;
317
+
use serde_json::json;
318
+
319
+
pub async fn api_tonight_events(
320
+
State(context): State<WebContext>,
321
+
Query(params): Query<HashMap<String, String>>,
322
+
) -> Result<Json<Value>, AppError> {
323
+
let location = params.get("location")
324
+
.ok_or_else(|| AppError::BadRequest("location parameter required".to_string()))?;
325
+
326
+
let radius = params.get("radius")
327
+
.and_then(|r| r.parse::<f64>().ok())
328
+
.unwrap_or(10.0);
329
+
330
+
let service = FilteringService::new(context.storage_pool.clone());
331
+
let events = TonightEventsTemplate::get_tonights_events(
332
+
&service,
333
+
location,
334
+
radius
335
+
).await?;
336
+
337
+
Ok(Json(json!({
338
+
"success": true,
339
+
"data": {
340
+
"events": events.events,
341
+
"total_count": events.total_count,
342
+
"location": location,
343
+
"radius_km": radius
344
+
}
345
+
})))
346
+
}
347
+
```
348
+
349
+
## API Reference
350
+
351
+
### FilteringService Methods
352
+
353
+
```rust
354
+
impl FilteringService {
355
+
/// Create a new filtering service instance
356
+
pub fn new(pool: sqlx::PgPool) -> Self;
357
+
358
+
/// Filter events with full criteria support
359
+
pub async fn filter_events(
360
+
&self,
361
+
criteria: &EventFilterCriteria,
362
+
page: i64,
363
+
page_size: i64,
364
+
) -> Result<FilteredEventsResult, FilteringError>;
365
+
366
+
/// Get facet counts for refining filters
367
+
pub async fn calculate_facets(
368
+
&self,
369
+
criteria: &EventFilterCriteria,
370
+
) -> Result<EventFacets, FilteringError>;
371
+
372
+
/// Hydrate events with additional data
373
+
pub async fn hydrate_events(
374
+
&self,
375
+
events: &mut [Event],
376
+
strategy: HydrationStrategy,
377
+
) -> Result<(), FilteringError>;
378
+
}
379
+
```
380
+
381
+
### EventFilterCriteria Fields
382
+
383
+
```rust
384
+
pub struct EventFilterCriteria {
385
+
/// Text search across event content
386
+
pub search_text: Option<String>,
387
+
388
+
/// Filter by date range
389
+
pub date_from: Option<DateTime<Utc>>,
390
+
pub date_to: Option<DateTime<Utc>>,
391
+
392
+
/// Location-based filtering
393
+
pub location_text: Option<String>,
394
+
pub location_latitude: Option<f64>,
395
+
pub location_longitude: Option<f64>,
396
+
pub location_radius_km: Option<f64>,
397
+
398
+
/// Event type filtering
399
+
pub event_types: Option<Vec<String>>,
400
+
401
+
/// Organizer filtering
402
+
pub organizer_handles: Option<Vec<String>>,
403
+
404
+
/// Sorting options
405
+
pub sort_by: Option<String>, // "date_asc", "date_desc", "popularity_desc", "relevance"
406
+
407
+
/// Language filtering
408
+
pub languages: Option<Vec<String>>,
409
+
}
410
+
```
411
+
412
+
## Template Usage
413
+
414
+
### 1. Tech Events Page Template
415
+
416
+
```handlebars
417
+
{{!-- templates/tech_events_page.en-us.html --}}
418
+
<div class="tech-events-page">
419
+
<h1>{{tr "tech-events-title"}}</h1>
420
+
<p class="subtitle">{{tr "tech-events-subtitle"}}</p>
421
+
422
+
<div class="events-grid">
423
+
{{#each events}}
424
+
<div class="event-card">
425
+
<h3><a href="/{{organizer_handle}}/{{rkey}}">{{title}}</a></h3>
426
+
<p class="event-date">{{format_date start_time}}</p>
427
+
<p class="event-location">{{location.name}}</p>
428
+
<p class="event-description">{{truncate description 150}}</p>
429
+
</div>
430
+
{{/each}}
431
+
</div>
432
+
433
+
{{#if (gt total_count events.length)}}
434
+
<p class="more-events">
435
+
<a href="/events?search=technology OR programming OR developer">
436
+
{{tr "view-all-tech-events"}}
437
+
</a>
438
+
</p>
439
+
{{/if}}
440
+
</div>
441
+
```
442
+
443
+
### 2. Weekend Events Widget
444
+
445
+
```handlebars
446
+
{{!-- templates/weekend_events_widget.en-us.incl.html --}}
447
+
<div class="weekend-widget"
448
+
hx-get="/api/weekend-events?city={{city}}"
449
+
hx-trigger="every 30m">
450
+
451
+
<h4>{{tr "this-weekend-in"}} {{city}}</h4>
452
+
453
+
{{#if events}}
454
+
<ul class="event-list">
455
+
{{#each events}}
456
+
<li class="event-item">
457
+
<a href="/{{organizer_handle}}/{{rkey}}">
458
+
<strong>{{title}}</strong>
459
+
<span class="event-time">{{format_time start_time}}</span>
460
+
</a>
461
+
</li>
462
+
{{/each}}
463
+
</ul>
464
+
{{else}}
465
+
<p class="no-events">{{tr "no-weekend-events"}}</p>
466
+
{{/if}}
467
+
</div>
468
+
```
469
+
470
+
### 3. Tonight's Events Notification
471
+
472
+
```handlebars
473
+
{{!-- templates/tonight_events_notification.en-us.incl.html --}}
474
+
{{#if events}}
475
+
<div class="notification is-info">
476
+
<button class="delete" onclick="this.parentElement.style.display='none'"></button>
477
+
<strong>{{tr "happening-tonight"}}:</strong>
478
+
{{#each events}}
479
+
<a href="/{{organizer_handle}}/{{rkey}}">{{title}}</a>{{#unless @last}}, {{/unless}}
480
+
{{/each}}
481
+
</div>
482
+
{{/if}}
483
+
```
484
+
485
+
## Performance Optimization
486
+
487
+
### 1. Caching Fixed Templates
488
+
489
+
```rust
490
+
use redis::AsyncCommands;
491
+
492
+
impl TechEventsTemplate {
493
+
pub async fn get_cached_tech_events(
494
+
service: &FilteringService,
495
+
redis: &mut redis::aio::Connection,
496
+
location: Option<String>,
497
+
) -> Result<FilteredEventsResult, FilteringError> {
498
+
let cache_key = format!("tech_events:{}",
499
+
location.as_deref().unwrap_or("global"));
500
+
501
+
// Try cache first
502
+
if let Ok(cached) = redis.get::<_, String>(&cache_key).await {
503
+
if let Ok(events) = serde_json::from_str(&cached) {
504
+
return Ok(events);
505
+
}
506
+
}
507
+
508
+
// Fallback to database
509
+
let events = Self::get_upcoming_tech_events(service, location).await?;
510
+
511
+
// Cache for 15 minutes
512
+
let serialized = serde_json::to_string(&events)?;
513
+
let _: () = redis.setex(&cache_key, 900, serialized).await?;
514
+
515
+
Ok(events)
516
+
}
517
+
}
518
+
```
519
+
520
+
### 2. Background Updates
521
+
522
+
```rust
523
+
use tokio::time::{interval, Duration};
524
+
525
+
pub async fn start_template_cache_updater(
526
+
service: FilteringService,
527
+
redis_pool: redis::aio::ConnectionManager,
528
+
) {
529
+
let mut interval = interval(Duration::from_secs(600)); // 10 minutes
530
+
531
+
loop {
532
+
interval.tick().await;
533
+
534
+
// Update popular templates
535
+
let cities = vec!["Montreal", "Toronto", "Vancouver", "Calgary"];
536
+
537
+
for city in cities {
538
+
if let Ok(mut conn) = redis_pool.clone().into_connection().await {
539
+
let _ = TechEventsTemplate::get_cached_tech_events(
540
+
&service,
541
+
&mut conn,
542
+
Some(city.to_string())
543
+
).await;
544
+
545
+
let _ = CommunityEventsTemplate::get_weekend_community_events(
546
+
&service,
547
+
city
548
+
).await;
549
+
}
550
+
}
551
+
}
552
+
}
553
+
```
554
+
555
+
## Error Handling
556
+
557
+
### 1. Graceful Degradation
558
+
559
+
```rust
560
+
pub async fn handle_tech_events_safe(
561
+
service: &FilteringService,
562
+
location: Option<String>,
563
+
) -> FilteredEventsResult {
564
+
match TechEventsTemplate::get_upcoming_tech_events(service, location).await {
565
+
Ok(events) => events,
566
+
Err(err) => {
567
+
tracing::error!("Failed to fetch tech events: {}", err);
568
+
569
+
// Return empty result with error indication
570
+
FilteredEventsResult {
571
+
events: vec![],
572
+
facets: EventFacets::default(),
573
+
total_count: 0,
574
+
has_more: false,
575
+
error_message: Some("Unable to load events at this time".to_string()),
576
+
}
577
+
}
578
+
}
579
+
}
580
+
```
581
+
582
+
### 2. Fallback Templates
583
+
584
+
```rust
585
+
impl TechEventsTemplate {
586
+
pub async fn get_tech_events_with_fallback(
587
+
service: &FilteringService,
588
+
location: Option<String>,
589
+
) -> Result<FilteredEventsResult, FilteringError> {
590
+
// Try specific tech events first
591
+
if let Ok(events) = Self::get_upcoming_tech_events(service, location.clone()).await {
592
+
if !events.events.is_empty() {
593
+
return Ok(events);
594
+
}
595
+
}
596
+
597
+
// Fallback to broader search
598
+
let criteria = EventFilterCriteria {
599
+
search_text: Some("event OR meetup OR conference".to_string()),
600
+
date_from: Some(Utc::now()),
601
+
date_to: Some(Utc::now() + Duration::days(30)),
602
+
location_text: location,
603
+
location_radius_km: Some(50.0),
604
+
sort_by: Some("date_asc".to_string()),
605
+
..Default::default()
606
+
};
607
+
608
+
service.filter_events(&criteria, 1, 10).await
609
+
}
610
+
}
611
+
```
612
+
613
+
## Integration Checklist
614
+
615
+
### Before Using Fixed Templates
616
+
617
+
- [ ] Database migrations applied (`20250530104334_event_filtering_indexes.sql`)
618
+
- [ ] Environment variable `DATABASE_URL` configured
619
+
- [ ] Redis connection available (for caching)
620
+
- [ ] Handlebars templates created for your use case
621
+
- [ ] Localization strings added to `i18n/*/ui.ftl` files
622
+
- [ ] Error handling implemented for your specific needs
623
+
624
+
### Template Implementation Steps
625
+
626
+
1. **Define your fixed criteria** in a template struct
627
+
2. **Implement the query method** using `EventFilterCriteria`
628
+
3. **Create the route handler** in your Axum router
629
+
4. **Add the Handlebars template** for rendering
630
+
5. **Add localization strings** for user-facing text
631
+
6. **Implement caching** for frequently-used templates
632
+
7. **Add error handling** and fallback behavior
633
+
8. **Test with realistic data** to verify performance
634
+
635
+
### Performance Considerations
636
+
637
+
- **Cache frequently-used templates** (tech events, weekend events)
638
+
- **Use background jobs** to pre-populate cache
639
+
- **Implement fallback queries** for when specific searches return no results
640
+
- **Monitor query performance** and adjust indexes as needed
641
+
- **Consider pagination** for templates that might return many results
642
+
643
+
---
644
+
645
+
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.
646
+
647
+
*Generated: May 30, 2025
648
+
Author: GitHub Copilot
649
+
Version: Usage Guide v1.0*