use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::{Deserialize, Serialize}; // API request/response types #[derive(Debug, Deserialize)] pub struct CalDAVLoginRequest { pub username: String, pub password: String, pub server_url: String, } #[derive(Debug, Serialize)] pub struct AuthResponse { pub token: String, pub session_token: String, pub username: String, pub server_url: String, pub preferences: UserPreferencesResponse, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct UserPreferencesResponse { pub calendar_selected_date: Option, pub calendar_time_increment: Option, pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_style: Option, pub calendar_colors: Option, pub last_used_calendar: Option, } #[derive(Debug, Deserialize)] pub struct UpdatePreferencesRequest { pub calendar_selected_date: Option, pub calendar_time_increment: Option, pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_style: Option, pub calendar_colors: Option, pub last_used_calendar: Option, } #[derive(Debug, Serialize)] pub struct UserInfo { pub username: String, pub server_url: String, pub calendars: Vec, } #[derive(Debug, Serialize)] pub struct CalendarInfo { pub path: String, pub display_name: String, pub color: String, pub is_visible: bool, } #[derive(Debug, Deserialize)] pub struct CreateCalendarRequest { pub name: String, pub description: Option, pub color: Option, } #[derive(Debug, Serialize)] pub struct CreateCalendarResponse { pub success: bool, pub message: String, } #[derive(Debug, Deserialize)] pub struct DeleteCalendarRequest { pub path: String, } #[derive(Debug, Serialize)] pub struct DeleteCalendarResponse { pub success: bool, pub message: String, } #[derive(Debug, Deserialize)] pub struct DeleteEventRequest { pub calendar_path: String, pub event_href: String, pub delete_action: String, // "delete_this", "delete_following", or "delete_series" pub occurrence_date: Option, // ISO date string for the specific occurrence } #[derive(Debug, Serialize)] pub struct DeleteEventResponse { pub success: bool, pub message: String, } #[derive(Debug, Deserialize)] pub struct CreateEventRequest { pub title: String, pub description: String, pub start_date: String, // YYYY-MM-DD format pub start_time: String, // HH:MM format pub end_date: String, // YYYY-MM-DD format pub end_time: String, // HH:MM format pub location: String, pub all_day: bool, pub status: String, // confirmed, tentative, cancelled pub class: String, // public, private, confidential pub priority: Option, // 0-9 priority level pub organizer: String, // organizer email pub attendees: String, // comma-separated attendee emails pub categories: String, // comma-separated categories pub reminder: String, // reminder type pub recurrence: String, // recurrence type pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence pub calendar_path: Option, // Optional - use first calendar if not specified } #[derive(Debug, Serialize)] pub struct CreateEventResponse { pub success: bool, pub message: String, pub event_href: Option, // The created event's href/filename } #[derive(Debug, Deserialize)] pub struct UpdateEventRequest { pub uid: String, // Event UID to identify which event to update pub title: String, pub description: String, pub start_date: String, // YYYY-MM-DD format pub start_time: String, // HH:MM format pub end_date: String, // YYYY-MM-DD format pub end_time: String, // HH:MM format pub location: String, pub all_day: bool, pub status: String, // confirmed, tentative, cancelled pub class: String, // public, private, confidential pub priority: Option, // 0-9 priority level pub organizer: String, // organizer email pub attendees: String, // comma-separated attendee emails pub categories: String, // comma-separated categories pub reminder: String, // reminder type pub recurrence: String, // recurrence type pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence pub calendar_path: Option, // Optional - search all calendars if not specified pub update_action: Option, // "update_series" for recurring events #[serde(skip_serializing_if = "Option::is_none")] pub until_date: Option, // ISO datetime string for RRULE UNTIL clause } #[derive(Debug, Serialize)] pub struct UpdateEventResponse { pub success: bool, pub message: String, } // ==================== EVENT SERIES MODELS ==================== #[derive(Debug, Deserialize)] pub struct CreateEventSeriesRequest { pub title: String, pub description: String, pub start_date: String, // YYYY-MM-DD format pub start_time: String, // HH:MM format pub end_date: String, // YYYY-MM-DD format pub end_time: String, // HH:MM format pub location: String, pub all_day: bool, pub status: String, // confirmed, tentative, cancelled pub class: String, // public, private, confidential pub priority: Option, // 0-9 priority level pub organizer: String, // organizer email pub attendees: String, // comma-separated attendee emails pub categories: String, // comma-separated categories pub reminder: String, // reminder type // Series-specific fields pub recurrence: String, // recurrence type (daily, weekly, monthly, yearly) pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence pub recurrence_interval: Option, // Every N days/weeks/months/years pub recurrence_end_date: Option, // When the series ends (YYYY-MM-DD) pub recurrence_count: Option, // Number of occurrences pub calendar_path: Option, // Optional - search all calendars if not specified } #[derive(Debug, Serialize)] pub struct CreateEventSeriesResponse { pub success: bool, pub message: String, pub series_uid: Option, // The base UID for the series pub occurrences_created: Option, // Number of individual events created pub event_href: Option, // The created series' href/filename } #[derive(Debug, Deserialize)] pub struct UpdateEventSeriesRequest { pub series_uid: String, // Series UID to identify which series to update pub title: String, pub description: String, pub start_date: String, // YYYY-MM-DD format pub start_time: String, // HH:MM format pub end_date: String, // YYYY-MM-DD format pub end_time: String, // HH:MM format pub location: String, pub all_day: bool, pub status: String, // confirmed, tentative, cancelled pub class: String, // public, private, confidential pub priority: Option, // 0-9 priority level pub organizer: String, // organizer email pub attendees: String, // comma-separated attendee emails pub categories: String, // comma-separated categories pub reminder: String, // reminder type // Series-specific fields pub recurrence: String, // recurrence type (daily, weekly, monthly, yearly) pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence pub recurrence_interval: Option, // Every N days/weeks/months/years pub recurrence_end_date: Option, // When the series ends (YYYY-MM-DD) pub recurrence_count: Option, // Number of occurrences pub calendar_path: Option, // Optional - search all calendars if not specified // Update scope control pub update_scope: String, // "this_only", "this_and_future", "all_in_series" pub occurrence_date: Option, // ISO date string for specific occurrence being updated pub changed_fields: Option>, // List of field names that were changed (for optimization) } #[derive(Debug, Serialize)] pub struct UpdateEventSeriesResponse { pub success: bool, pub message: String, pub series_uid: Option, pub occurrences_affected: Option, // Number of events updated } #[derive(Debug, Deserialize)] pub struct DeleteEventSeriesRequest { pub series_uid: String, // Series UID to identify which series to delete pub calendar_path: String, pub event_href: String, // Delete scope control pub delete_scope: String, // "this_only", "this_and_future", "all_in_series" pub occurrence_date: Option, // ISO date string for specific occurrence being deleted } #[derive(Debug, Serialize)] pub struct DeleteEventSeriesResponse { pub success: bool, pub message: String, pub occurrences_affected: Option, // Number of events deleted } // Error handling #[derive(Debug)] pub enum ApiError { Database(String), NotFound(String), Unauthorized(String), BadRequest(String), Conflict(String), Internal(String), } impl IntoResponse for ApiError { fn into_response(self) -> Response { let (status, error_message) = match self { ApiError::Database(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg), ApiError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg), ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg), ApiError::Conflict(msg) => (StatusCode::CONFLICT, msg), ApiError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), }; let body = Json(serde_json::json!({ "error": error_message, "status": status.as_u16() })); (status, body).into_response() } } impl std::fmt::Display for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ApiError::Database(msg) => write!(f, "Database error: {}", msg), ApiError::NotFound(msg) => write!(f, "Not found: {}", msg), ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg), ApiError::BadRequest(msg) => write!(f, "Bad request: {}", msg), ApiError::Conflict(msg) => write!(f, "Conflict: {}", msg), ApiError::Internal(msg) => write!(f, "Internal error: {}", msg), } } } impl std::error::Error for ApiError {}