From 8ae429ac54dc5921c9e3e7d24071d41da4e1a396 Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Sat, 20 Sep 2025 12:39:56 +0200 Subject: [PATCH] Implement create Icalendar endpoint --- src/main.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index ce09d36..d0d41f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ use axum::extract::Query; -use axum::response::IntoResponse; +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 serde::{Deserialize, Serialize}; -use std::ops::{Add, Sub}; +use icalendar::{Calendar, Class, Component, Event, EventLike}; +use serde::Deserialize; +use std::ops::Sub; #[derive(Deserialize)] enum Recurring { @@ -68,24 +70,66 @@ struct EventBody { /// TODO what if start_date is weekend async fn get_calendar(Query(query): Query) -> impl IntoResponse { let start = query.start_date_time; - let start_day = start.day(); 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 { - previous_date_time = previous_date_time.with_day(start_day).unwrap(); - let next = match query.recurring { + let next = match recurring { Recurring::Daily => todo!(), Recurring::Weeky => todo!(), Recurring::BiWeekly => todo!(), Recurring::Monthly => { - let new_date = previous_date_time - .checked_add_months(Months::new(1)) - .unwrap(); - if let Some(true) = query.avoid_weekends { + 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)), @@ -101,12 +145,14 @@ async fn get_calendar(Query(query): Query) -> impl IntoResponse { events.push(next); previous_date_time = next; } - Json(events) + events } #[tokio::main] async fn main() { - let app = Router::new().route("/", get(get_calendar)); + 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();