use axum::{extract::State, http::HeaderMap, response::Json}; use std::sync::Arc; use crate::calendar::CalDAVClient; use crate::{ models::{ApiError, AuthResponse, CalDAVLoginRequest, CalendarInfo, UserInfo}, AppState, }; pub fn extract_bearer_token(headers: &HeaderMap) -> Result { let auth_header = headers .get("authorization") .ok_or_else(|| ApiError::Unauthorized("Missing Authorization header".to_string()))?; let auth_str = auth_header .to_str() .map_err(|_| ApiError::BadRequest("Invalid Authorization header".to_string()))?; if let Some(token) = auth_str.strip_prefix("Bearer ") { Ok(token.to_string()) } else { Err(ApiError::BadRequest( "Authorization header must be Bearer token".to_string(), )) } } pub fn extract_password_header(headers: &HeaderMap) -> Result { let password_header = headers .get("x-caldav-password") .ok_or_else(|| ApiError::BadRequest("Missing X-CalDAV-Password header".to_string()))?; password_header .to_str() .map(|s| s.to_string()) .map_err(|_| ApiError::BadRequest("Invalid X-CalDAV-Password header".to_string())) } pub async fn login( State(state): State>, Json(request): Json, ) -> Result, ApiError> { println!("🔐 Login attempt:"); println!(" Server URL: {}", request.server_url); println!(" Username: {}", request.username); println!(" Password length: {}", request.password.len()); // Use the auth service login method which now handles database, sessions, and preferences let response = state.auth_service.login(request).await?; println!("✅ Login successful with session management"); Ok(Json(response)) } pub async fn verify_token( State(state): State>, headers: HeaderMap, ) -> Result, ApiError> { let token = extract_bearer_token(&headers)?; let is_valid = state.auth_service.verify_token(&token).is_ok(); Ok(Json(serde_json::json!({ "valid": is_valid }))) } pub async fn get_user_info( State(state): State>, headers: HeaderMap, ) -> Result, ApiError> { let token = extract_bearer_token(&headers)?; let password = extract_password_header(&headers)?; // Create CalDAV config from token and password let config = state .auth_service .caldav_config_from_token(&token, &password)?; let client = CalDAVClient::new(config.clone()); // Discover calendars let calendar_paths = client .discover_calendars() .await .map_err(|e| ApiError::Internal(format!("Failed to discover calendars: {}", e)))?; let calendars: Vec = calendar_paths .iter() .map(|path| CalendarInfo { path: path.clone(), display_name: extract_calendar_name(path), color: generate_calendar_color(path), is_visible: true, // Default to visible }) .collect(); Ok(Json(UserInfo { username: config.username, server_url: config.server_url, calendars, })) } fn generate_calendar_color(path: &str) -> String { // Generate a consistent color based on the calendar path // This is a simple hash-based approach let mut hash: u32 = 0; for byte in path.bytes() { hash = hash.wrapping_mul(31).wrapping_add(byte as u32); } // Define a set of pleasant colors let colors = [ "#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#06B6D4", "#84CC16", "#F97316", "#EC4899", "#6366F1", "#14B8A6", "#F3B806", "#8B5A2B", "#6B7280", "#DC2626", "#7C3AED", "#059669", "#D97706", "#BE185D", "#4F46E5", ]; colors[(hash as usize) % colors.len()].to_string() } fn extract_calendar_name(path: &str) -> String { // Extract calendar name from path // E.g., "/calendars/user/calendar-name/" -> "Calendar Name" path.split('/') .filter(|s| !s.is_empty()) .last() .unwrap_or("Calendar") .replace('-', " ") .replace('_', " ") .split_whitespace() .map(|word| { let mut chars = word.chars(); match chars.next() { None => String::new(), Some(first) => first.to_uppercase().collect::() + chars.as_str(), } }) .collect::>() .join(" ") }