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:
@@ -8,6 +8,7 @@ use crate::models::ical::VEvent;
|
||||
use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService};
|
||||
use chrono::NaiveDate;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use gloo_timers::callback::Interval;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::MouseEvent;
|
||||
use yew::prelude::*;
|
||||
@@ -79,6 +80,7 @@ pub fn App() -> Html {
|
||||
let external_calendars = use_state(|| -> Vec<ExternalCalendar> { Vec::new() });
|
||||
let external_calendar_events = use_state(|| -> Vec<VEvent> { Vec::new() });
|
||||
let external_calendar_modal_open = use_state(|| false);
|
||||
let refresh_interval = use_state(|| -> Option<Interval> { None });
|
||||
|
||||
// Calendar view state - load from localStorage if available
|
||||
let current_view = use_state(|| {
|
||||
@@ -307,51 +309,77 @@ pub fn App() -> Html {
|
||||
});
|
||||
}
|
||||
|
||||
// Load external calendars when auth token is available
|
||||
// Function to refresh external calendars
|
||||
let refresh_external_calendars = {
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
Callback::from(move |_| {
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
// Load external calendars
|
||||
match CalendarService::get_external_calendars().await {
|
||||
Ok(calendars) => {
|
||||
external_calendars.set(calendars.clone());
|
||||
|
||||
// Load events for visible external calendars
|
||||
let mut all_events = Vec::new();
|
||||
for calendar in calendars {
|
||||
if calendar.is_visible {
|
||||
if let Ok(mut events) = CalendarService::fetch_external_calendar_events(calendar.id).await {
|
||||
// Set calendar_path for color matching
|
||||
for event in &mut events {
|
||||
event.calendar_path = Some(format!("external_{}", calendar.id));
|
||||
}
|
||||
all_events.extend(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
external_calendar_events.set(all_events);
|
||||
}
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(
|
||||
&format!("Failed to load external calendars: {}", err).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
// Load external calendars when auth token is available and set up auto-refresh
|
||||
{
|
||||
let auth_token = auth_token.clone();
|
||||
let refresh_external_calendars = refresh_external_calendars.clone();
|
||||
let refresh_interval = refresh_interval.clone();
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
|
||||
use_effect_with((*auth_token).clone(), move |token| {
|
||||
if token.is_some() {
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
// Load external calendars
|
||||
match CalendarService::get_external_calendars().await {
|
||||
Ok(calendars) => {
|
||||
external_calendars.set(calendars.clone());
|
||||
|
||||
// Load events for visible external calendars
|
||||
let mut all_events = Vec::new();
|
||||
for calendar in calendars {
|
||||
if calendar.is_visible {
|
||||
if let Ok(mut events) = CalendarService::fetch_external_calendar_events(calendar.id).await {
|
||||
// Set calendar_path for color matching
|
||||
for event in &mut events {
|
||||
event.calendar_path = Some(format!("external_{}", calendar.id));
|
||||
}
|
||||
all_events.extend(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
external_calendar_events.set(all_events);
|
||||
}
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(
|
||||
&format!("Failed to load external calendars: {}", err).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(_) = token {
|
||||
// Initial load
|
||||
refresh_external_calendars.emit(());
|
||||
|
||||
// Set up 5-minute refresh interval
|
||||
let refresh_external_calendars = refresh_external_calendars.clone();
|
||||
let interval = Interval::new(5 * 60 * 1000, move || {
|
||||
refresh_external_calendars.emit(());
|
||||
});
|
||||
refresh_interval.set(Some(interval));
|
||||
} else {
|
||||
// Clear data and interval when logged out
|
||||
external_calendars.set(Vec::new());
|
||||
external_calendar_events.set(Vec::new());
|
||||
refresh_interval.set(None);
|
||||
}
|
||||
|
||||
|| ()
|
||||
// Cleanup function
|
||||
let refresh_interval = refresh_interval.clone();
|
||||
move || {
|
||||
// Clear interval on cleanup
|
||||
refresh_interval.set(None);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1011,7 +1039,7 @@ pub fn App() -> Html {
|
||||
|
||||
external_calendars.set(calendars.clone());
|
||||
|
||||
// Reload events for all visible external calendars
|
||||
// Reload events for all visible external calendars
|
||||
let mut all_events = Vec::new();
|
||||
for cal in calendars {
|
||||
if cal.is_visible {
|
||||
@@ -1062,6 +1090,42 @@ pub fn App() -> Html {
|
||||
});
|
||||
}
|
||||
})}
|
||||
on_external_calendar_refresh={Callback::from({
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
let external_calendars = external_calendars.clone();
|
||||
move |id: i32| {
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
let external_calendars = external_calendars.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
// Force refresh of this specific calendar
|
||||
if let Ok(mut events) = CalendarService::fetch_external_calendar_events(id).await {
|
||||
// Set calendar_path for color matching
|
||||
for event in &mut events {
|
||||
event.calendar_path = Some(format!("external_{}", id));
|
||||
}
|
||||
|
||||
// Update events for this calendar
|
||||
let mut all_events = (*external_calendar_events).clone();
|
||||
// Remove old events from this calendar
|
||||
all_events.retain(|e| {
|
||||
if let Some(ref calendar_path) = e.calendar_path {
|
||||
calendar_path != &format!("external_{}", id)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
// Add new events
|
||||
all_events.extend(events);
|
||||
external_calendar_events.set(all_events);
|
||||
|
||||
// Update the last_fetched timestamp in calendars list
|
||||
if let Ok(calendars) = CalendarService::get_external_calendars().await {
|
||||
external_calendars.set(calendars);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})}
|
||||
color_picker_open={(*color_picker_open).clone()}
|
||||
on_color_change={on_color_change}
|
||||
on_color_picker_toggle={on_color_picker_toggle}
|
||||
@@ -1355,42 +1419,7 @@ pub fn App() -> Html {
|
||||
let external_calendar_modal_open = external_calendar_modal_open.clone();
|
||||
move |_| external_calendar_modal_open.set(false)
|
||||
})}
|
||||
on_success={Callback::from({
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
move |_| {
|
||||
// Reload external calendars
|
||||
let external_calendars = external_calendars.clone();
|
||||
let external_calendar_events = external_calendar_events.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match CalendarService::get_external_calendars().await {
|
||||
Ok(calendars) => {
|
||||
external_calendars.set(calendars.clone());
|
||||
|
||||
// Load events for visible external calendars
|
||||
let mut all_events = Vec::new();
|
||||
for calendar in calendars {
|
||||
if calendar.is_visible {
|
||||
if let Ok(mut events) = CalendarService::fetch_external_calendar_events(calendar.id).await {
|
||||
// Set calendar_path for color matching
|
||||
for event in &mut events {
|
||||
event.calendar_path = Some(format!("external_{}", calendar.id));
|
||||
}
|
||||
all_events.extend(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
external_calendar_events.set(all_events);
|
||||
}
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(
|
||||
&format!("Failed to reload external calendars: {}", err).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})}
|
||||
on_success={refresh_external_calendars.clone()}
|
||||
/>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
||||
Reference in New Issue
Block a user