Add intelligent caching and auto-refresh for external calendars
Implements server-side database caching with 5-minute refresh intervals to dramatically improve external calendar performance while keeping data fresh. Backend changes: - New external_calendar_cache table with ICS data storage - Smart cache logic: serves from cache if < 5min old, fetches fresh otherwise - Cache repository methods for get/update/clear operations - Migration script for cache table creation Frontend changes: - 5-minute auto-refresh interval for background updates - Manual refresh button (🔄) for each external calendar - Last updated timestamps showing when each calendar was refreshed - Centralized refresh function with proper cleanup on logout Performance improvements: - Initial load: instant from cache vs slow external HTTP requests - Background updates: fresh data without user waiting - Reduced external API calls: only when cache is stale - Scalable: handles multiple external calendars efficiently 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		| @@ -1,4 +1,4 @@ | ||||
| use chrono::{DateTime, Utc}; | ||||
| use chrono::{DateTime, Duration, Utc}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; | ||||
| use sqlx::{FromRow, Result}; | ||||
| @@ -427,4 +427,62 @@ impl<'a> ExternalCalendarRepository<'a> { | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Get cached ICS data for an external calendar | ||||
|     pub async fn get_cached_data(&self, external_calendar_id: i32) -> Result<Option<(String, DateTime<Utc>)>> { | ||||
|         let result = sqlx::query_as::<_, (String, DateTime<Utc>)>( | ||||
|             "SELECT ics_data, cached_at FROM external_calendar_cache WHERE external_calendar_id = ?", | ||||
|         ) | ||||
|         .bind(external_calendar_id) | ||||
|         .fetch_optional(self.db.pool()) | ||||
|         .await?; | ||||
|  | ||||
|         Ok(result) | ||||
|     } | ||||
|  | ||||
|     /// Update cache with new ICS data | ||||
|     pub async fn update_cache(&self, external_calendar_id: i32, ics_data: &str, etag: Option<&str>) -> Result<()> { | ||||
|         sqlx::query( | ||||
|             "INSERT INTO external_calendar_cache (external_calendar_id, ics_data, etag, cached_at) | ||||
|              VALUES (?, ?, ?, ?) | ||||
|              ON CONFLICT(external_calendar_id) DO UPDATE SET | ||||
|              ics_data = excluded.ics_data, | ||||
|              etag = excluded.etag, | ||||
|              cached_at = excluded.cached_at", | ||||
|         ) | ||||
|         .bind(external_calendar_id) | ||||
|         .bind(ics_data) | ||||
|         .bind(etag) | ||||
|         .bind(Utc::now()) | ||||
|         .execute(self.db.pool()) | ||||
|         .await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Check if cache is stale (older than max_age_minutes) | ||||
|     pub async fn is_cache_stale(&self, external_calendar_id: i32, max_age_minutes: i64) -> Result<bool> { | ||||
|         let cutoff_time = Utc::now() - Duration::minutes(max_age_minutes); | ||||
|          | ||||
|         let result = sqlx::query_scalar::<_, i64>( | ||||
|             "SELECT COUNT(*) FROM external_calendar_cache  | ||||
|              WHERE external_calendar_id = ? AND cached_at > ?", | ||||
|         ) | ||||
|         .bind(external_calendar_id) | ||||
|         .bind(cutoff_time) | ||||
|         .fetch_one(self.db.pool()) | ||||
|         .await?; | ||||
|  | ||||
|         Ok(result == 0) | ||||
|     } | ||||
|  | ||||
|     /// Clear cache for an external calendar | ||||
|     pub async fn clear_cache(&self, external_calendar_id: i32) -> Result<()> { | ||||
|         sqlx::query("DELETE FROM external_calendar_cache WHERE external_calendar_id = ?") | ||||
|             .bind(external_calendar_id) | ||||
|             .execute(self.db.pool()) | ||||
|             .await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone