diff --git a/.idea/runConfigurations/Run_recurring_event_api.xml b/.idea/runConfigurations/Run_recurring_event_api.xml new file mode 100644 index 0000000..27957c8 --- /dev/null +++ b/.idea/runConfigurations/Run_recurring_event_api.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/src/handler/ics.rs b/src/handler/ics.rs new file mode 100644 index 0000000..48c08dd --- /dev/null +++ b/src/handler/ics.rs @@ -0,0 +1,46 @@ +use crate::handler::EventQuery; +use crate::service::date_service::get_dates; +use axum::extract::Query; +use axum::http::header::CONTENT_TYPE; +use axum::response::{IntoResponse, Response}; +use axum::routing::get; +use axum::Router; +use chrono::TimeDelta; +use icalendar::{Calendar, Class, Component, Event, EventLike}; + +const TEXT_CALENDAR: &'static str = "text/calendar"; + +pub(super) fn router() -> Router { + Router::new().route("/ics", get(get_ics)) +} + +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() +} diff --git a/src/handler/index.rs b/src/handler/index.rs new file mode 100644 index 0000000..9c883e9 --- /dev/null +++ b/src/handler/index.rs @@ -0,0 +1,24 @@ +use crate::handler::EventQuery; +use crate::service::date_service::get_dates; +use axum::extract::Query; +use axum::response::IntoResponse; +use axum::routing::get; +use axum::{Json, Router}; +use chrono::TimeDelta; + +pub(super) fn router() -> Router { + Router::new().route("/", get(get_calendar)) +} + +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), + )) +} diff --git a/src/handler/mod.rs b/src/handler/mod.rs new file mode 100644 index 0000000..89b7555 --- /dev/null +++ b/src/handler/mod.rs @@ -0,0 +1,27 @@ +use crate::model::models::{Move, Recurring}; +use axum::Router; +use chrono::{NaiveDate, NaiveDateTime}; +use serde::Deserialize; + +mod ics; +mod index; + +pub(crate) fn router() -> Router { + Router::new().merge(index::router()).merge(ics::router()) +} + +#[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, +} diff --git a/src/main.rs b/src/main.rs index f403220..99fc0d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,163 +1,18 @@ -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; +mod handler; +mod model; +mod service; -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 -} +use axum::Router; #[tokio::main] async fn main() { const PORT: &'static str = "8000"; - let app = Router::new() - .route("/", get(get_calendar)) - .route("/ics", get(get_ics)); + let app = Router::new().merge(handler::router()); println!("Starting Application on port {PORT}"); - let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{PORT}")).await.unwrap(); + let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{PORT}")) + .await + .unwrap(); axum::serve(listener, app).await.unwrap(); } diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..3605c98 --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1 @@ +pub(crate) mod models; diff --git a/src/model/models.rs b/src/model/models.rs new file mode 100644 index 0000000..b4be348 --- /dev/null +++ b/src/model/models.rs @@ -0,0 +1,17 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub enum Recurring { + Daily, + Weeky, + BiWeekly, + Monthly, + Quarterly, + Yearly, +} + +#[derive(Deserialize)] +pub enum Move { + Before, + After, +} diff --git a/src/service/date_service.rs b/src/service/date_service.rs new file mode 100644 index 0000000..b7be209 --- /dev/null +++ b/src/service/date_service.rs @@ -0,0 +1,39 @@ +use crate::model::models::Recurring; +use chrono::{Datelike, Months, NaiveDate, NaiveDateTime, TimeDelta, Weekday}; + +/// TODO implement Before / After +/// TODO what if start_date is weekend +pub 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 - TimeDelta::days(1), + Weekday::Sun => baseline - TimeDelta::days(2), + _ => baseline, + } + } else { + baseline + } + } + Recurring::Quarterly => todo!(), + Recurring::Yearly => todo!(), + }; + events.push(next); + previous_date_time = next; + } + events +} diff --git a/src/service/mod.rs b/src/service/mod.rs new file mode 100644 index 0000000..6097c33 --- /dev/null +++ b/src/service/mod.rs @@ -0,0 +1 @@ +pub(crate) mod date_service;