diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3f7142b --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# CalDAV Server Configuration +CALDAV_SERVER_URL=https://your-caldav-server.com/dav/ +CALDAV_USERNAME=your-username +CALDAV_PASSWORD=your-password + +# Optional: Calendar collection path (if different from default) +CALDAV_CALENDAR_PATH=/calendars/your-username/personal/ + +# Optional: Task/Todo collection path +CALDAV_TASKS_PATH=/calendars/your-username/tasks/ + +# Development settings +RUST_LOG=info \ No newline at end of file diff --git a/.gitignore b/.gitignore index 524406b..6f4d404 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,9 @@ dist/ .idea/ # Logs -*.log \ No newline at end of file +*.log + +# Environment variables (secrets) +.env +.env.local +.env.*.local \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 91d47a6..3faf5a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,32 @@ edition = "2021" [dependencies] yew = { version = "0.21", features = ["csr"] } web-sys = "0.3" -wasm-bindgen = "0.2" \ No newline at end of file +wasm-bindgen = "0.2" + +# HTTP client for CalDAV requests +reqwest = { version = "0.11", features = ["json"] } +wasm-bindgen-futures = "0.4" + +# Calendar and iCal parsing +ical = "0.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# Date and time handling +chrono = { version = "0.4", features = ["serde", "wasm-bindgen"] } +chrono-tz = "0.8" + +# Error handling +anyhow = "1.0" +thiserror = "1.0" + +# Logging +log = "0.4" +console_log = "1.0" + +# UUID generation for calendar events +uuid = { version = "1.0", features = ["v4", "wasm-bindgen"] } + +# Environment variable handling +dotenvy = "0.15" +base64 = "0.21" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d45d848 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,182 @@ +use serde::{Deserialize, Serialize}; +use std::env; + +/// Configuration for CalDAV server connection and authentication. +/// +/// This struct holds all the necessary information to connect to a CalDAV server, +/// including server URL, credentials, and optional collection paths. +/// +/// # Security Note +/// +/// The password field contains sensitive information and should be handled carefully. +/// This struct implements `Debug` but in production, consider implementing a custom +/// `Debug` that masks the password field. +/// +/// # Example +/// +/// ```rust +/// use crate::config::CalDAVConfig; +/// +/// // Load configuration from environment variables +/// let config = CalDAVConfig::from_env()?; +/// +/// // Use the configuration for HTTP requests +/// let auth_header = format!("Basic {}", config.get_basic_auth()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CalDAVConfig { + /// The base URL of the CalDAV server (e.g., "https://caldav.example.com/dav/") + pub server_url: String, + + /// Username for authentication with the CalDAV server + pub username: String, + + /// Password for authentication with the CalDAV server + /// + /// **Security Note**: This contains sensitive information + pub password: String, + + /// Optional path to the calendar collection on the server + /// + /// If not provided, the client will need to discover available calendars + /// through CalDAV PROPFIND requests + pub calendar_path: Option, + + /// Optional path to the tasks/todo collection on the server + /// + /// Some CalDAV servers store tasks separately from calendar events + pub tasks_path: Option, +} + +impl CalDAVConfig { + /// Creates a new CalDAVConfig by loading values from environment variables. + /// + /// This method will attempt to load a `.env` file from the current directory + /// and then read the following required environment variables: + /// + /// - `CALDAV_SERVER_URL`: The CalDAV server base URL + /// - `CALDAV_USERNAME`: Username for authentication + /// - `CALDAV_PASSWORD`: Password for authentication + /// + /// Optional environment variables: + /// + /// - `CALDAV_CALENDAR_PATH`: Path to calendar collection + /// - `CALDAV_TASKS_PATH`: Path to tasks collection + /// + /// # Errors + /// + /// Returns `ConfigError::MissingVar` if any required environment variable + /// is not set or cannot be read. + /// + /// # Example + /// + /// ```rust + /// use crate::config::CalDAVConfig; + /// + /// match CalDAVConfig::from_env() { + /// Ok(config) => { + /// println!("Loaded config for server: {}", config.server_url); + /// } + /// Err(e) => { + /// eprintln!("Failed to load config: {}", e); + /// } + /// } + /// ``` + pub fn from_env() -> Result { + // Attempt to load .env file, but don't fail if it doesn't exist + dotenvy::dotenv().ok(); + + let server_url = env::var("CALDAV_SERVER_URL") + .map_err(|_| ConfigError::MissingVar("CALDAV_SERVER_URL".to_string()))?; + + let username = env::var("CALDAV_USERNAME") + .map_err(|_| ConfigError::MissingVar("CALDAV_USERNAME".to_string()))?; + + let password = env::var("CALDAV_PASSWORD") + .map_err(|_| ConfigError::MissingVar("CALDAV_PASSWORD".to_string()))?; + + // Optional paths - it's fine if these are not set + let calendar_path = env::var("CALDAV_CALENDAR_PATH").ok(); + let tasks_path = env::var("CALDAV_TASKS_PATH").ok(); + + Ok(CalDAVConfig { + server_url, + username, + password, + calendar_path, + tasks_path, + }) + } + + /// Generates a Base64-encoded string for HTTP Basic Authentication. + /// + /// This method combines the username and password in the format + /// `username:password` and encodes it using Base64, which is the + /// standard format for the `Authorization: Basic` HTTP header. + /// + /// # Returns + /// + /// A Base64-encoded string that can be used directly in the + /// `Authorization` header: `Authorization: Basic ` + /// + /// # Example + /// + /// ```rust + /// use crate::config::CalDAVConfig; + /// + /// let config = CalDAVConfig { + /// server_url: "https://example.com".to_string(), + /// username: "user".to_string(), + /// password: "pass".to_string(), + /// calendar_path: None, + /// tasks_path: None, + /// }; + /// + /// let auth_value = config.get_basic_auth(); + /// let auth_header = format!("Basic {}", auth_value); + /// ``` + pub fn get_basic_auth(&self) -> String { + let credentials = format!("{}:{}", self.username, self.password); + base64::encode(&credentials) + } +} + +/// Errors that can occur when loading or using CalDAV configuration. +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + /// A required environment variable is missing or cannot be read. + /// + /// This error occurs when calling `CalDAVConfig::from_env()` and one of the + /// required environment variables (`CALDAV_SERVER_URL`, `CALDAV_USERNAME`, + /// or `CALDAV_PASSWORD`) is not set. + #[error("Missing environment variable: {0}")] + MissingVar(String), + + /// The configuration contains invalid or malformed values. + /// + /// This could include malformed URLs, invalid authentication credentials, + /// or other configuration issues that prevent proper CalDAV operation. + #[error("Invalid configuration: {0}")] + Invalid(String), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn test_basic_auth_encoding() { + let config = CalDAVConfig { + server_url: "https://example.com".to_string(), + username: "testuser".to_string(), + password: "testpass".to_string(), + calendar_path: None, + tasks_path: None, + }; + + let auth = config.get_basic_auth(); + let expected = base64::encode("testuser:testpass"); + assert_eq!(auth, expected); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 056c44b..f1006a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use yew::prelude::*; mod app; +mod config; + use app::App; fn main() {