use axum::extract::Query; use axum::http::header::CONTENT_TYPE; use axum::response::{IntoResponse, Response}; use axum::routing::get; use axum::{Json, Router}; use chrono::{Datelike, Months, NaiveDate, NaiveDateTime, TimeDelta, Weekday}; use icalendar::{Calendar, Class, Component, Event, EventLike}; use serde::Deserialize; use std::ops::Sub; const TEXT_CALENDAR: &'static str = "text/calendar"; #[derive(Deserialize)] enum Recurring { Daily, Weeky, BiWeekly, Monthly, Quarterly, Yearly, } #[derive(Deserialize)] enum Move { Before, After, } #[derive(Deserialize)] struct EventQuery { /// Start date start_date_time: NaiveDateTime, /// Recurring date recurring: Recurring, // If set, will ignore time on start_date all_day: bool, /// Title of the event title: String, // Optionals end_date: Option, avoid_weekends: Option, on_collition: Option, } #[derive(Deserialize)] enum Time { /// Specific time at day DateTime { start: NaiveDateTime, end: NaiveDateTime, }, /// All day Date(NaiveDate), } #[derive(Deserialize)] struct EventBody { /// Start date time: Time, /// Recurring date recurring: Recurring, /// Title of the event title: String, // Optionals end_date: Option, ignore_weekdays: Vec, on_collition: Option, } /// TODO create ical object /// TODO implement Before / After /// TODO what if start_date is weekend async fn get_calendar(Query(query): Query) -> impl IntoResponse { let start = query.start_date_time; let end = query .end_date .unwrap_or_else(|| (start + TimeDelta::days(365)).date()); Json(get_dates( start, end, query.recurring, query.avoid_weekends.unwrap_or(false), )) } async fn get_ics(Query(query): Query) -> impl IntoResponse { let start = query.start_date_time; let end = query .end_date .unwrap_or_else(|| (start + TimeDelta::days(365)).date()); let mut calendar = Calendar::new(); calendar.name(&query.title); get_dates( start, end, query.recurring, query.avoid_weekends.unwrap_or(false), ) .into_iter() .for_each(|date_time| { let mut event = Event::new(); if query.all_day { event.all_day(date_time.date()); } else { event.starts(date_time); }; event.summary(&query.title).class(Class::Private); calendar.push(event); }); Response::builder() .header(CONTENT_TYPE, TEXT_CALENDAR) .body(calendar.done().to_string()) .unwrap() } fn get_dates( start: NaiveDateTime, end: NaiveDate, recurring: Recurring, avoid_weekends: bool, ) -> Vec { let mut events = vec![start]; let mut previous_date_time = start; let mut baseline = start; while previous_date_time.date() < end { let next = match recurring { Recurring::Daily => todo!(), Recurring::Weeky => todo!(), Recurring::BiWeekly => todo!(), Recurring::Monthly => { baseline = baseline.checked_add_months(Months::new(1)).unwrap(); if let true = avoid_weekends { match baseline.weekday() { Weekday::Sat => baseline.sub(TimeDelta::days(1)), Weekday::Sun => baseline.sub(TimeDelta::days(2)), _ => baseline, } } else { baseline } } Recurring::Quarterly => todo!(), Recurring::Yearly => todo!(), }; events.push(next); previous_date_time = next; } events } #[tokio::main] async fn main() { const PORT: &'static str = "8000"; let app = Router::new() .route("/", get(get_calendar)) .route("/ics", get(get_ics)); println!("Starting Application on port {PORT}"); let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{PORT}")).await.unwrap(); axum::serve(listener, app).await.unwrap(); }