ticker-serve/export: implement ics export

This commit is contained in:
Astro 2023-03-20 01:14:01 +01:00
parent 608471667c
commit 11fa975fb2
5 changed files with 96 additions and 0 deletions

3
ticker-serve/askama.toml Normal file
View File

@ -0,0 +1,3 @@
[[escaper]]
path = "Text"
extensions = ["ics"]

View File

@ -0,0 +1,62 @@
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 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(app_state.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().collect::<Vec<Event>>();
let (template, content_type) = f(es);
match template.render() {
Ok(rendered) =>
(
[(HeaderName::from_static("content-type"), HeaderValue::from_static(content_type))]
.iter().cloned()
.collect::<HeaderMap>(),
rendered
).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template. Error: {}", e),
).into_response(),
}
}

View File

@ -12,6 +12,7 @@ use diesel::{Connection, pg::PgConnection};
use libticker::config::Config;
mod index;
mod export;
#[derive(Clone)]
pub struct AppState {
@ -33,6 +34,7 @@ async fn main() {
};
let app = Router::new()
.route("/", get(index::index))
.route("/export.ics", get(export::ics))
.layer(Extension(state))
.nest_service("/static", ServeDir::new("static"));
axum::Server::bind(&http_bind)

View File

@ -0,0 +1,24 @@
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-WR-TIMEZONE;VALUE=TEXT:Europe/Berlin
{% for e in events -%}
BEGIN:VEVENT
SUMMARY:{{ e.summary }}
DTSTART:{{ e.dtstart.format("%Y%m%dT%H%M%S").to_string() }}
{% if let Some(dtend) = e.dtend -%}
DTEND:{{ dtend.format("%Y%m%dT%H%M%S") }}
{% endif -%}
UID:{{ e.id.replace(char::is_whitespace, "_") }}
{% if let Some(url) = e.url -%}
URL:{{ url }}
{% endif -%}
{% if let Some(location) = e.location -%}
LOCATION:{{ location }}
{% endif -%}
END:VEVENT
{% endfor -%}
END:VCALENDAR

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width"/>
<link rel="stylesheet" title="Style" type="text/css" href="static/style.css"/>
<link rel="icon" type="image/png" href="static/favicon.png"/>
<link rel="alternate" type="text/calendar" title="ICS" href="export.ics"/>
</head>
<body>
<h1>Dresden Ticker</h1>
@ -62,6 +63,10 @@
</div>
{% endfor %}
<footer>
<p>
Maschinenlesbarer Export:
<a href="export.rss">ICS</a>
</p>
<p>
Ein Projekt des
<a href="https://www.c3d2.de/">C3D2</a>