90 lines
2.7 KiB
Rust
90 lines
2.7 KiB
Rust
use askama::{Template, Text};
|
|
use axum::{
|
|
response::{IntoResponse, Response},
|
|
http::{StatusCode, HeaderMap, HeaderName, HeaderValue},
|
|
Extension,
|
|
};
|
|
|
|
use diesel::prelude::*;
|
|
use chrono::{offset::Local, Duration};
|
|
|
|
use libticker::{
|
|
schema::{self, events::dsl::events},
|
|
model::Event,
|
|
};
|
|
use crate::AppState;
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "export.ics")]
|
|
struct IcsTemplate {
|
|
events: Vec<Event>,
|
|
}
|
|
|
|
pub async fn ics(Extension(app_state): Extension<AppState>) -> Response {
|
|
export(app_state, |es| (
|
|
IcsTemplate { events: es },
|
|
"text/calendar"
|
|
)).await
|
|
}
|
|
|
|
async fn export<F, T>(app_state: AppState, f: F) -> Response
|
|
where
|
|
F: FnOnce(Vec<Event>) -> (T, &'static str),
|
|
T: Template,
|
|
{
|
|
let config = &app_state.config;
|
|
let db = app_state.db.lock().unwrap();
|
|
let today = Local::now().date_naive().and_hms_opt(0, 0, 0).unwrap();
|
|
let limit = Local::now().date_naive().and_hms_opt(0, 0, 0).unwrap() +
|
|
Duration::days(config.upcoming_days.into());
|
|
let es: Vec<Event> = events
|
|
.filter(schema::events::dtend.ge(&today))
|
|
.or_filter(schema::events::dtstart.ge(&today))
|
|
.filter(schema::events::dtstart.lt(&limit))
|
|
.order_by(schema::events::dtstart.asc())
|
|
.then_order_by(schema::events::dtend.desc())
|
|
.load::<Event>(&*db)
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|mut e| {
|
|
if e.location.as_ref().map_or(false, |s| s.is_empty()) {
|
|
e.location = None;
|
|
}
|
|
if e.url.as_ref().map_or(false, |s| s.is_empty()) {
|
|
e.url = None;
|
|
}
|
|
e
|
|
})
|
|
.map(|mut e| {
|
|
let defaults = config.calendars.get(&e.calendar)
|
|
.and_then(|calendar| calendar.defaults.as_ref());
|
|
let defaults = match defaults {
|
|
Some(defaults) => defaults,
|
|
None => return e,
|
|
};
|
|
if e.location.is_none() {
|
|
e.location = defaults.location.clone();
|
|
}
|
|
if e.url.is_none() {
|
|
e.url = defaults.url.clone();
|
|
}
|
|
e
|
|
}).collect::<Vec<_>>();
|
|
let (template, content_type) = f(es);
|
|
match template.render() {
|
|
Ok(rendered) => {
|
|
let ics = rendered.replace('\n', "\r\n");
|
|
(
|
|
[(HeaderName::from_static("content-type"), HeaderValue::from_static(content_type))]
|
|
.iter().cloned()
|
|
.collect::<HeaderMap>(),
|
|
ics
|
|
).into_response()
|
|
}
|
|
Err(e) => (
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
format!("Failed to render template. Error: {}", e),
|
|
).into_response(),
|
|
}
|
|
}
|