5 Commits

Author SHA1 Message Date
Connor Johnstone
fd80624429 Fix frontend compilation warnings by removing dead code
All checks were successful
Build and Push Docker Image / docker (push) Successful in 4m5s
- Remove unused Route enum and yew_router import from sidebar.rs
- Remove unused last_fetched field from ExternalCalendarEventsResponse
- Prefix unused variables with underscore (_preserve_rrule, _until_date, etc.)

Frontend now compiles cleanly with no warnings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 16:28:44 -04:00
Connor Johnstone
b530dcaa69 Remove unnecessary frontend console logs
- Remove app rendering auth_token debug logs
- Remove auth token validation success message
- Remove recurring event occurrence generation logs
- Remove exception dates logging for VEvents

Frontend now runs with minimal essential logging.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 16:20:55 -04:00
Connor Johnstone
0821573041 Remove remaining verbose CalDAV discovery and authentication logs
- Remove discovery response XML dumps that flood console
- Remove calendar collection checking logs
- Remove authentication success messages
- Remove API call password length logging
- Fix unused variable warning

Backend now runs with minimal essential logging only.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 16:15:30 -04:00
Connor Johnstone
703c9ee2f5 Clean up debug logging and fix compiler warnings
- Remove non-critical debug logs from backend CalDAV parsing
- Remove all-day event debug logs from frontend components
- Fix unused variable warnings with underscore prefixes
- Keep essential logging while reducing noise

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 16:02:41 -04:00
Connor Johnstone
5854ad291d Fix all-day event parsing with VALUE=DATE format support
- Add special handling for date-only format (%Y%m%d) in parse_datetime_with_tz
- Convert NaiveDate to midnight NaiveDateTime for all-day events
- Reorder all-day detection to occur before datetime parsing
- All-day events now properly display in calendar views

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 15:51:00 -04:00
10 changed files with 43 additions and 126 deletions

View File

@@ -39,19 +39,13 @@ impl AuthService {
request.username.clone(), request.username.clone(),
request.password.clone(), request.password.clone(),
); );
println!("📝 Created CalDAV config");
// Test authentication against CalDAV server // Test authentication against CalDAV server
let caldav_client = CalDAVClient::new(caldav_config.clone()); let caldav_client = CalDAVClient::new(caldav_config.clone());
println!("🔗 Created CalDAV client, attempting to discover calendars...");
// Try to discover calendars as an authentication test // Try to discover calendars as an authentication test
match caldav_client.discover_calendars().await { match caldav_client.discover_calendars().await {
Ok(calendars) => { Ok(_calendars) => {
println!(
"✅ Authentication successful! Found {} calendars",
calendars.len()
);
// Find or create user in database // Find or create user in database
let user_repo = UserRepository::new(&self.db); let user_repo = UserRepository::new(&self.db);

View File

@@ -167,8 +167,6 @@ impl CalDAVClient {
}; };
let basic_auth = self.config.get_basic_auth(); let basic_auth = self.config.get_basic_auth();
println!("🔑 REPORT Basic Auth: Basic {}", basic_auth);
println!("🌐 REPORT URL: {}", url);
let response = self let response = self
.http_client .http_client
@@ -349,6 +347,8 @@ impl CalDAVClient {
} }
} }
full_prop.push_str(&format!(":{}", prop_value)); full_prop.push_str(&format!(":{}", prop_value));
full_properties.insert(prop_name, full_prop); full_properties.insert(prop_name, full_prop);
} }
@@ -358,6 +358,13 @@ impl CalDAVClient {
.ok_or_else(|| CalDAVError::ParseError("Missing UID field".to_string()))? .ok_or_else(|| CalDAVError::ParseError("Missing UID field".to_string()))?
.clone(); .clone();
// Determine if it's an all-day event FIRST by checking for VALUE=DATE parameter per RFC 5545
let empty_string = String::new();
let dtstart_raw = full_properties.get("DTSTART").unwrap_or(&empty_string);
let dtstart_value = properties.get("DTSTART").unwrap_or(&empty_string);
let all_day = dtstart_raw.contains("VALUE=DATE") || (!dtstart_value.contains("T") && dtstart_value.len() == 8);
// Parse start time (required) // Parse start time (required)
let start_prop = properties let start_prop = properties
.get("DTSTART") .get("DTSTART")
@@ -375,11 +382,6 @@ impl CalDAVClient {
(None, None) (None, None)
}; };
// Determine if it's an all-day event by checking for VALUE=DATE parameter
let empty_string = String::new();
let dtstart_raw = properties.get("DTSTART").unwrap_or(&empty_string);
let all_day = dtstart_raw.contains("VALUE=DATE") || (!dtstart_raw.contains("T") && dtstart_raw.len() == 8);
// Parse status // Parse status
let status = properties let status = properties
.get("STATUS") .get("STATUS")
@@ -582,11 +584,9 @@ impl CalDAVClient {
pub async fn discover_calendars(&self) -> Result<Vec<String>, CalDAVError> { pub async fn discover_calendars(&self) -> Result<Vec<String>, CalDAVError> {
// First, try to discover user calendars if we have a calendar path in config // First, try to discover user calendars if we have a calendar path in config
if let Some(calendar_path) = &self.config.calendar_path { if let Some(calendar_path) = &self.config.calendar_path {
println!("Using configured calendar path: {}", calendar_path);
return Ok(vec![calendar_path.clone()]); return Ok(vec![calendar_path.clone()]);
} }
println!("No calendar path configured, discovering calendars...");
// Try different common CalDAV discovery paths // Try different common CalDAV discovery paths
// Note: paths should be relative to the server URL base // Note: paths should be relative to the server URL base
@@ -599,20 +599,16 @@ impl CalDAVClient {
let mut has_valid_caldav_response = false; let mut has_valid_caldav_response = false;
for path in discovery_paths { for path in discovery_paths {
println!("Trying discovery path: {}", path);
match self.discover_calendars_at_path(&path).await { match self.discover_calendars_at_path(&path).await {
Ok(calendars) => { Ok(calendars) => {
println!("Found {} calendar(s) at {}", calendars.len(), path);
has_valid_caldav_response = true; has_valid_caldav_response = true;
all_calendars.extend(calendars); all_calendars.extend(calendars);
} }
Err(CalDAVError::ServerError(status)) => { Err(CalDAVError::ServerError(_status)) => {
// HTTP error - this might be expected for some paths, continue trying // HTTP error - this might be expected for some paths, continue trying
println!("Discovery path {} returned HTTP {}, trying next path", path, status);
} }
Err(e) => { Err(e) => {
// Network or other error - this suggests the server isn't reachable or isn't CalDAV // Network or other error - this suggests the server isn't reachable or isn't CalDAV
println!("Discovery failed for path {}: {:?}", path, e);
return Err(e); return Err(e);
} }
} }
@@ -669,7 +665,6 @@ impl CalDAVClient {
} }
let body = response.text().await.map_err(CalDAVError::RequestError)?; let body = response.text().await.map_err(CalDAVError::RequestError)?;
println!("Discovery response for {}: {}", path, body);
let mut calendar_paths = Vec::new(); let mut calendar_paths = Vec::new();
@@ -680,7 +675,6 @@ impl CalDAVClient {
// Extract href first // Extract href first
if let Some(href) = self.extract_xml_content(response_content, "href") { if let Some(href) = self.extract_xml_content(response_content, "href") {
println!("🔍 Checking resource: {}", href);
// Check if this is a calendar collection by looking for supported-calendar-component-set // Check if this is a calendar collection by looking for supported-calendar-component-set
// This indicates it's an actual calendar that can contain events // This indicates it's an actual calendar that can contain events
@@ -704,14 +698,10 @@ impl CalDAVClient {
&& !href.ends_with("/calendars/") && !href.ends_with("/calendars/")
&& href.ends_with('/') && href.ends_with('/')
{ {
println!("📅 Found calendar collection: {}", href);
calendar_paths.push(href); calendar_paths.push(href);
} else { } else {
println!("❌ Skipping system/root directory: {}", href);
} }
} else { } else {
println!(" Not a calendar collection: {} (is_calendar: {}, has_collection: {})",
href, is_calendar, has_collection);
} }
} }
} }
@@ -786,14 +776,24 @@ impl CalDAVClient {
return Ok((dt.naive_utc(), Some("UTC".to_string()))); return Ok((dt.naive_utc(), Some("UTC".to_string())));
} }
// Try parsing as naive datetime // Special handling for date-only format (all-day events)
if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_part, format) { if *format == "%Y%m%d" {
// Per RFC 5545: if no TZID parameter is provided, treat as UTC if let Ok(date) = chrono::NaiveDate::parse_from_str(datetime_part, format) {
let tz = timezone_id.unwrap_or_else(|| "UTC".to_string()); // Convert date to midnight datetime for all-day events
let naive_dt = date.and_hms_opt(0, 0, 0).unwrap();
// If it's UTC, the naive time is already correct let tz = timezone_id.unwrap_or_else(|| "UTC".to_string());
// If it's a local timezone, we store the naive time and the timezone ID return Ok((naive_dt, Some(tz)));
return Ok((naive_dt, Some(tz))); }
} else {
// Try parsing as naive datetime for time-based formats
if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_part, format) {
// Per RFC 5545: if no TZID parameter is provided, treat as UTC
let tz = timezone_id.unwrap_or_else(|| "UTC".to_string());
// If it's UTC, the naive time is already correct
// If it's a local timezone, we store the naive time and the timezone ID
return Ok((naive_dt, Some(tz)));
}
} }
} }

View File

@@ -82,10 +82,6 @@ pub async fn get_user_info(
.await .await
.map_err(|e| ApiError::Internal(format!("Failed to discover calendars: {}", e)))?; .map_err(|e| ApiError::Internal(format!("Failed to discover calendars: {}", e)))?;
println!(
"✅ Authentication successful! Found {} calendars",
calendar_paths.len()
);
let calendars: Vec<CalendarInfo> = calendar_paths let calendars: Vec<CalendarInfo> = calendar_paths
.iter() .iter()

View File

@@ -35,7 +35,6 @@ pub async fn get_calendar_events(
// Extract and verify token // Extract and verify token
let token = extract_bearer_token(&headers)?; let token = extract_bearer_token(&headers)?;
let password = extract_password_header(&headers)?; let password = extract_password_header(&headers)?;
println!("🔑 API call with password length: {}", password.len());
// Create CalDAV config from token and password // Create CalDAV config from token and password
let config = state let config = state
@@ -127,7 +126,6 @@ pub async fn get_calendar_events(
}); });
} }
println!("📅 Returning {} events", all_events.len());
Ok(Json(all_events)) Ok(Json(all_events))
} }

View File

@@ -491,26 +491,18 @@ fn deduplicate_events(mut events: Vec<VEvent>) -> Vec<VEvent> {
let mut uid_groups: HashMap<String, Vec<VEvent>> = HashMap::new(); let mut uid_groups: HashMap<String, Vec<VEvent>> = HashMap::new();
for event in events.drain(..) { for event in events.drain(..) {
// Debug logging to understand what's happening
println!("🔍 Event: '{}' at {} (RRULE: {}) - UID: {}",
event.summary.as_ref().unwrap_or(&"No Title".to_string()),
event.dtstart.format("%Y-%m-%d %H:%M"),
if event.rrule.is_some() { "Yes" } else { "No" },
event.uid
);
uid_groups.entry(event.uid.clone()).or_insert_with(Vec::new).push(event); uid_groups.entry(event.uid.clone()).or_insert_with(Vec::new).push(event);
} }
let mut uid_deduplicated_events = Vec::new(); let mut uid_deduplicated_events = Vec::new();
for (uid, mut events_with_uid) in uid_groups.drain() { for (_uid, mut events_with_uid) in uid_groups.drain() {
if events_with_uid.len() == 1 { if events_with_uid.len() == 1 {
// Only one event with this UID, keep it // Only one event with this UID, keep it
uid_deduplicated_events.push(events_with_uid.into_iter().next().unwrap()); uid_deduplicated_events.push(events_with_uid.into_iter().next().unwrap());
} else { } else {
// Multiple events with same UID - prefer recurring over non-recurring // Multiple events with same UID - prefer recurring over non-recurring
println!("🔍 Found {} events with UID '{}'", events_with_uid.len(), uid);
// Sort by preference: recurring events first, then by completeness // Sort by preference: recurring events first, then by completeness
events_with_uid.sort_by(|a, b| { events_with_uid.sort_by(|a, b| {
@@ -529,10 +521,6 @@ fn deduplicate_events(mut events: Vec<VEvent>) -> Vec<VEvent> {
// Keep the first (preferred) event // Keep the first (preferred) event
let preferred_event = events_with_uid.into_iter().next().unwrap(); let preferred_event = events_with_uid.into_iter().next().unwrap();
println!("🔄 UID dedup: Keeping '{}' (RRULE: {})",
preferred_event.summary.as_ref().unwrap_or(&"No Title".to_string()),
if preferred_event.rrule.is_some() { "Yes" } else { "No" }
);
uid_deduplicated_events.push(preferred_event); uid_deduplicated_events.push(preferred_event);
} }
} }

View File

@@ -71,7 +71,6 @@ pub fn App() -> Html {
match auth_service.verify_token(&stored_token).await { match auth_service.verify_token(&stored_token).await {
Ok(true) => { Ok(true) => {
// Token is valid, set it // Token is valid, set it
web_sys::console::log_1(&"✅ Stored auth token is valid".into());
auth_token.set(Some(stored_token)); auth_token.set(Some(stored_token));
} }
_ => { _ => {
@@ -915,8 +914,8 @@ pub fn App() -> Html {
original_event, original_event,
new_start, new_start,
new_end, new_end,
preserve_rrule, _preserve_rrule,
until_date, _until_date,
update_scope, update_scope,
occurrence_date, occurrence_date,
): ( ): (
@@ -1198,10 +1197,6 @@ pub fn App() -> Html {
}) })
}; };
// Debug logging
web_sys::console::log_1(
&format!("App rendering: auth_token = {:?}", auth_token.is_some()).into(),
);
html! { html! {
<BrowserRouter> <BrowserRouter>
@@ -1479,7 +1474,7 @@ pub fn App() -> Html {
let calendar_management_modal_open = calendar_management_modal_open.clone(); let calendar_management_modal_open = calendar_management_modal_open.clone();
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
let calendar_service = CalendarService::new(); let _calendar_service = CalendarService::new();
match CalendarService::get_external_calendars().await { match CalendarService::get_external_calendars().await {
Ok(calendars) => { Ok(calendars) => {
external_calendars.set(calendars); external_calendars.set(calendars);

View File

@@ -113,6 +113,7 @@ pub fn month_view(props: &MonthViewProps) -> Html {
"#3B82F6".to_string() "#3B82F6".to_string()
}; };
html! { html! {
<div class="calendar-grid"> <div class="calendar-grid">
// Weekday headers // Weekday headers

View File

@@ -2,17 +2,7 @@ use crate::components::CalendarListItem;
use crate::services::calendar_service::{UserInfo, ExternalCalendar}; use crate::services::calendar_service::{UserInfo, ExternalCalendar};
use web_sys::HtmlSelectElement; use web_sys::HtmlSelectElement;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*;
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/")]
Home,
#[at("/login")]
Login,
#[at("/calendar")]
Calendar,
}
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum ViewMode { pub enum ViewMode {

View File

@@ -348,6 +348,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
}) })
}; };
html! { html! {
<div class="week-view-container"> <div class="week-view-container">
// Header with weekday names and dates // Header with weekday names and dates

View File

@@ -247,6 +247,8 @@ impl CalendarService {
if resp.ok() { if resp.ok() {
let events: Vec<CalendarEvent> = serde_json::from_str(&text_string) let events: Vec<CalendarEvent> = serde_json::from_str(&text_string)
.map_err(|e| format!("JSON parsing failed: {}", e))?; .map_err(|e| format!("JSON parsing failed: {}", e))?;
Ok(events) Ok(events)
} else { } else {
Err(format!( Err(format!(
@@ -277,14 +279,15 @@ impl CalendarService {
/// Convert UTC events to local timezone for display /// Convert UTC events to local timezone for display
fn convert_utc_to_local(mut event: VEvent) -> VEvent { fn convert_utc_to_local(mut event: VEvent) -> VEvent {
// All-day events should not have timezone conversions applied
if event.all_day {
return event;
}
// Check if event times are in UTC (legacy events from before timezone migration) // Check if event times are in UTC (legacy events from before timezone migration)
let is_utc_event = event.dtstart_tzid.as_ref().map_or(true, |tz| tz == "UTC"); let is_utc_event = event.dtstart_tzid.as_ref().map_or(true, |tz| tz == "UTC");
if is_utc_event { if is_utc_event {
web_sys::console::log_1(&format!(
"🕐 Converting UTC event '{}' to local time",
event.summary.as_deref().unwrap_or("Untitled")
).into());
// Get current timezone offset (convert from UTC to local) // Get current timezone offset (convert from UTC to local)
let date = js_sys::Date::new_0(); let date = js_sys::Date::new_0();
@@ -330,38 +333,10 @@ impl CalendarService {
// Convert UTC events to local time for proper display // Convert UTC events to local time for proper display
let event = Self::convert_utc_to_local(event); let event = Self::convert_utc_to_local(event);
if let Some(ref rrule) = event.rrule { if let Some(ref rrule) = event.rrule {
web_sys::console::log_1(
&format!(
"📅 Processing recurring VEvent '{}' with RRULE: {}",
event.summary.as_deref().unwrap_or("Untitled"),
rrule
)
.into(),
);
// Log if event has exception dates
if !event.exdate.is_empty() {
web_sys::console::log_1(
&format!(
"📅 VEvent '{}' has {} exception dates: {:?}",
event.summary.as_deref().unwrap_or("Untitled"),
event.exdate.len(),
event.exdate
)
.into(),
);
}
// Generate occurrences for recurring events using VEvent // Generate occurrences for recurring events using VEvent
let occurrences = Self::generate_occurrences(&event, rrule, start_range, end_range); let occurrences = Self::generate_occurrences(&event, rrule, start_range, end_range);
web_sys::console::log_1(
&format!(
"📅 Generated {} occurrences for VEvent '{}'",
occurrences.len(),
event.summary.as_deref().unwrap_or("Untitled")
)
.into(),
);
expanded_events.extend(occurrences); expanded_events.extend(occurrences);
} else { } else {
// Non-recurring event - add as-is // Non-recurring event - add as-is
@@ -383,7 +358,6 @@ impl CalendarService {
// Parse RRULE components // Parse RRULE components
let rrule_upper = rrule.to_uppercase(); let rrule_upper = rrule.to_uppercase();
web_sys::console::log_1(&format!("🔄 Parsing RRULE: {}", rrule_upper).into());
let components: HashMap<String, String> = rrule_upper let components: HashMap<String, String> = rrule_upper
.split(';') .split(';')
@@ -438,8 +412,7 @@ impl CalendarService {
} }
}); });
if let Some(until) = until_date { if let Some(_until) = until_date {
web_sys::console::log_1(&format!("📅 RRULE has UNTIL: {}", until).into());
} }
let start_date = base_event.dtstart.date(); let start_date = base_event.dtstart.date();
@@ -453,10 +426,6 @@ impl CalendarService {
let current_datetime = base_event.dtstart let current_datetime = base_event.dtstart
+ Duration::days(current_date.signed_duration_since(start_date).num_days()); + Duration::days(current_date.signed_duration_since(start_date).num_days());
if current_datetime > until { if current_datetime > until {
web_sys::console::log_1(
&format!("🛑 Stopping at {} due to UNTIL {}", current_datetime, until)
.into(),
);
break; break;
} }
} }
@@ -653,13 +622,6 @@ impl CalendarService {
let days_diff = occurrence_date.signed_duration_since(start_date).num_days(); let days_diff = occurrence_date.signed_duration_since(start_date).num_days();
let occurrence_datetime = base_event.dtstart + Duration::days(days_diff); let occurrence_datetime = base_event.dtstart + Duration::days(days_diff);
if occurrence_datetime > until { if occurrence_datetime > until {
web_sys::console::log_1(
&format!(
"🛑 Stopping at {} due to UNTIL {}",
occurrence_datetime, until
)
.into(),
);
return occurrences; return occurrences;
} }
} }
@@ -779,13 +741,6 @@ impl CalendarService {
occurrence_date.signed_duration_since(start_date).num_days(); occurrence_date.signed_duration_since(start_date).num_days();
let occurrence_datetime = base_event.dtstart + Duration::days(days_diff); let occurrence_datetime = base_event.dtstart + Duration::days(days_diff);
if occurrence_datetime > until { if occurrence_datetime > until {
web_sys::console::log_1(
&format!(
"🛑 Stopping at {} due to UNTIL {}",
occurrence_datetime, until
)
.into(),
);
return occurrences; return occurrences;
} }
} }
@@ -2146,7 +2101,6 @@ impl CalendarService {
#[derive(Deserialize)] #[derive(Deserialize)]
struct ExternalCalendarEventsResponse { struct ExternalCalendarEventsResponse {
events: Vec<VEvent>, events: Vec<VEvent>,
last_fetched: chrono::DateTime<chrono::Utc>,
} }
let response: ExternalCalendarEventsResponse = serde_wasm_bindgen::from_value(json) let response: ExternalCalendarEventsResponse = serde_wasm_bindgen::from_value(json)