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, } pub async fn ics(Extension(app_state): Extension) -> Response { export(app_state, |es| ( IcsTemplate { events: es }, "text/calendar" )).await } async fn export(app_state: AppState, f: F) -> Response where F: FnOnce(Vec) -> (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 = 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::(&*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::>(); 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::(), ics ).into_response() } Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to render template. Error: {}", e), ).into_response(), } }