Implement create Icalendar endpoint

This commit is contained in:
2025-09-20 12:39:56 +02:00
parent 8fe9b3f824
commit 8ae429ac54

View File

@ -1,10 +1,12 @@
use axum::extract::Query; 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::routing::get;
use axum::{Json, Router}; use axum::{Json, Router};
use chrono::{Datelike, Months, NaiveDate, NaiveDateTime, TimeDelta, Weekday}; use chrono::{Datelike, Months, NaiveDate, NaiveDateTime, TimeDelta, Weekday};
use serde::{Deserialize, Serialize}; use icalendar::{Calendar, Class, Component, Event, EventLike};
use std::ops::{Add, Sub}; use serde::Deserialize;
use std::ops::Sub;
#[derive(Deserialize)] #[derive(Deserialize)]
enum Recurring { enum Recurring {
@ -68,24 +70,66 @@ struct EventBody {
/// TODO what if start_date is weekend /// TODO what if start_date is weekend
async fn get_calendar(Query(query): Query<EventQuery>) -> impl IntoResponse { async fn get_calendar(Query(query): Query<EventQuery>) -> impl IntoResponse {
let start = query.start_date_time; let start = query.start_date_time;
let start_day = start.day();
let end = query let end = query
.end_date .end_date
.unwrap_or_else(|| (start + TimeDelta::days(365)).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<EventQuery>) -> 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<NaiveDateTime> {
let mut events = vec![start]; let mut events = vec![start];
let mut previous_date_time = start; let mut previous_date_time = start;
let mut baseline = start;
while previous_date_time.date() < end { while previous_date_time.date() < end {
previous_date_time = previous_date_time.with_day(start_day).unwrap(); let next = match recurring {
let next = match query.recurring {
Recurring::Daily => todo!(), Recurring::Daily => todo!(),
Recurring::Weeky => todo!(), Recurring::Weeky => todo!(),
Recurring::BiWeekly => todo!(), Recurring::BiWeekly => todo!(),
Recurring::Monthly => { Recurring::Monthly => {
let new_date = previous_date_time let new_date = baseline.checked_add_months(Months::new(1)).unwrap();
.checked_add_months(Months::new(1)) baseline = new_date;
.unwrap(); if let true = avoid_weekends {
if let Some(true) = query.avoid_weekends {
match new_date.weekday() { match new_date.weekday() {
Weekday::Sat => new_date.sub(TimeDelta::days(1)), Weekday::Sat => new_date.sub(TimeDelta::days(1)),
Weekday::Sun => new_date.sub(TimeDelta::days(2)), Weekday::Sun => new_date.sub(TimeDelta::days(2)),
@ -101,12 +145,14 @@ async fn get_calendar(Query(query): Query<EventQuery>) -> impl IntoResponse {
events.push(next); events.push(next);
previous_date_time = next; previous_date_time = next;
} }
Json(events) events
} }
#[tokio::main] #[tokio::main]
async fn 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(); let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();