Files
recurring-event-api/src/main.rs
2025-09-20 13:06:21 +02:00

161 lines
4.1 KiB
Rust

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<NaiveDate>,
avoid_weekends: Option<bool>,
on_collition: Option<Move>,
}
#[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<NaiveDate>,
ignore_weekdays: Vec<Weekday>,
on_collition: Option<Move>,
}
/// TODO create ical object
/// TODO implement Before / After
/// TODO what if start_date is weekend
async fn get_calendar(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());
Json(get_dates(
start,
end,
query.recurring,
query.avoid_weekends.unwrap_or(false),
))
}
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_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<NaiveDateTime> {
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() {
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();
}