scheduling_api/
lib.rs

1use axum::{extract::State, http::StatusCode, Json};
2use caldav_utils::{
3    availability::{get_availability, AvailabilityRequest, AvailabilityResponse},
4    caldav::calendar::Calendar,
5};
6use tracing::info;
7
8pub mod error;
9pub mod state;
10
11pub use crate::{
12    error::{SchedulerError, SchedulerResult},
13    state::CaldavAvailability,
14};
15
16pub async fn get_calendars(
17    client: &reqwest::Client,
18    caldav_state: CaldavAvailability,
19) -> SchedulerResult<(Calendar, Calendar)> {
20    let mut principal = caldav_state.davclient.get_principal(client).await?;
21
22    Ok((
23        principal
24            .get_calendar(client, &caldav_state.availability_calendar)
25            .await?,
26        principal
27            .get_calendar(client, &caldav_state.booked_calendar)
28            .await?,
29    ))
30}
31
32#[axum::debug_handler]
33pub async fn request_availability(
34    State(caldav_state): State<CaldavAvailability>,
35    body: Json<AvailabilityRequest>,
36) -> SchedulerResult<Json<AvailabilityResponse>> {
37    // TODO: validate the request. e.g. start < end, max range, etc.
38
39    // First, lookup events in the availability calendar
40    let client = reqwest::Client::new();
41    let (availability_calendar, booked_calendar) = get_calendars(&client, caldav_state).await?;
42    info!(
43        "Found calendars: {}, {}",
44        availability_calendar.path, booked_calendar.path
45    );
46
47    let granularity = chrono::Duration::minutes(30);
48
49    let avail = get_availability(
50        &client,
51        &availability_calendar,
52        &booked_calendar,
53        body.start,
54        body.end,
55        granularity,
56    )
57    .await?;
58
59    // Err(StatusCode::NOT_IMPLEMENTED)
60    Ok(Json(avail))
61}
62
63#[derive(Debug, serde::Deserialize)]
64pub struct BookingRequest {
65    pub start: chrono::DateTime<chrono::Utc>,
66    pub end: chrono::DateTime<chrono::Utc>,
67    pub name: String,
68    pub email: String,
69    pub description: String,
70}
71/// Attempt to reserve a time slot in the booked calendar
72/// This will fail if the slot is not available
73pub async fn request_booking(
74    State(caldav_state): State<CaldavAvailability>,
75    body: Json<BookingRequest>,
76) -> SchedulerResult<StatusCode> {
77    let client = reqwest::Client::new();
78    let (availability_calendar, booked_calendar) = get_calendars(&client, caldav_state).await?;
79
80    let granularity = chrono::Duration::minutes(30);
81
82    // First, retrieve the availability and check if the slot is available
83    let avail = get_availability(
84        &client,
85        &availability_calendar,
86        &booked_calendar,
87        body.start,
88        body.end,
89        granularity,
90    )
91    .await?;
92
93    /*
94     * Check if the requested time is available
95     */
96    let is_available = avail.matrix.iter().all(|it| *it);
97    if !is_available {
98        return Err(SchedulerError::TimeNotAvailable(body.start));
99    }
100
101    let description = format!("email: {}\n{}", body.email, body.description);
102
103    // Create an event in the booking calendar
104    booked_calendar
105        .create_event(&client, body.start, body.end, &body.name, &description)
106        .await?;
107
108    Ok(StatusCode::OK)
109}
110
111/// gets the current time
112pub async fn get_now() -> Result<Json<chrono::DateTime<chrono::Utc>>, StatusCode> {
113    Ok(Json(chrono::Utc::now()))
114}