ticker/ticker-serve/src/export.rs

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(),
}
}