Compare commits
1 Commits
2326bb6e21
...
master
Author | SHA1 | Date | |
---|---|---|---|
477c74d5d2
|
20
.idea/runConfigurations/Run_recurring_event_api.xml
generated
Normal file
20
.idea/runConfigurations/Run_recurring_event_api.xml
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run recurring-event-api" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="buildProfileId" value="dev" />
|
||||||
|
<option name="command" value="run --package recurring-event-api --bin recurring-event-api" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<envs />
|
||||||
|
<option name="emulateTerminal" value="true" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
46
src/handler/ics.rs
Normal file
46
src/handler/ics.rs
Normal file
@ -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<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()
|
||||||
|
}
|
24
src/handler/index.rs
Normal file
24
src/handler/index.rs
Normal file
@ -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<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),
|
||||||
|
))
|
||||||
|
}
|
27
src/handler/mod.rs
Normal file
27
src/handler/mod.rs
Normal file
@ -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<NaiveDate>,
|
||||||
|
avoid_weekends: Option<bool>,
|
||||||
|
on_collition: Option<Move>,
|
||||||
|
}
|
161
src/main.rs
161
src/main.rs
@ -1,163 +1,18 @@
|
|||||||
use axum::extract::Query;
|
mod handler;
|
||||||
use axum::http::header::CONTENT_TYPE;
|
mod model;
|
||||||
use axum::response::{IntoResponse, Response};
|
mod service;
|
||||||
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";
|
use axum::Router;
|
||||||
|
|
||||||
#[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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
const PORT: &'static str = "8000";
|
const PORT: &'static str = "8000";
|
||||||
let app = Router::new()
|
let app = Router::new().merge(handler::router());
|
||||||
.route("/", get(get_calendar))
|
|
||||||
.route("/ics", get(get_ics));
|
|
||||||
|
|
||||||
println!("Starting Application on port {PORT}");
|
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();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
1
src/model/mod.rs
Normal file
1
src/model/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod models;
|
17
src/model/models.rs
Normal file
17
src/model/models.rs
Normal file
@ -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,
|
||||||
|
}
|
39
src/service/date_service.rs
Normal file
39
src/service/date_service.rs
Normal file
@ -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<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 - 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
|
||||||
|
}
|
1
src/service/mod.rs
Normal file
1
src/service/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod date_service;
|
Reference in New Issue
Block a user