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; #[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), )) } const TEXT_CALENDAR: &'static str = "text/calendar"; 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| { calendar.push( Event::new() .summary(&query.title) .starts(date) .class(Class::Confidential), ); }); 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 => { let new_date = baseline.checked_add_months(Months::new(1)).unwrap(); baseline = new_date; if let true = avoid_weekends { match new_date.weekday() { Weekday::Sat => new_date.sub(TimeDelta::days(1)), Weekday::Sun => new_date.sub(TimeDelta::days(2)), _ => new_date, } } else { new_date } } Recurring::Quarterly => todo!(), Recurring::Yearly => todo!(), }; events.push(next); previous_date_time = next; } events } #[tokio::main] async fn main() { let app = Router::new() .route("/", get(get_calendar)) .route("/ics", get(get_ics)); let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }