ticker-serve/export: implement ics export
This commit is contained in:
parent
608471667c
commit
11fa975fb2
3
ticker-serve/askama.toml
Normal file
3
ticker-serve/askama.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[[escaper]]
|
||||||
|
path = "Text"
|
||||||
|
extensions = ["ics"]
|
62
ticker-serve/src/export.rs
Normal file
62
ticker-serve/src/export.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ use diesel::{Connection, pg::PgConnection};
|
||||||
|
|
||||||
use libticker::config::Config;
|
use libticker::config::Config;
|
||||||
mod index;
|
mod index;
|
||||||
|
mod export;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
@ -33,6 +34,7 @@ async fn main() {
|
||||||
};
|
};
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index::index))
|
.route("/", get(index::index))
|
||||||
|
.route("/export.ics", get(export::ics))
|
||||||
.layer(Extension(state))
|
.layer(Extension(state))
|
||||||
.nest_service("/static", ServeDir::new("static"));
|
.nest_service("/static", ServeDir::new("static"));
|
||||||
axum::Server::bind(&http_bind)
|
axum::Server::bind(&http_bind)
|
||||||
|
|
24
ticker-serve/templates/export.ics
Normal file
24
ticker-serve/templates/export.ics
Normal 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
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width"/>
|
<meta name="viewport" content="width=device-width"/>
|
||||||
<link rel="stylesheet" title="Style" type="text/css" href="static/style.css"/>
|
<link rel="stylesheet" title="Style" type="text/css" href="static/style.css"/>
|
||||||
<link rel="icon" type="image/png" href="static/favicon.png"/>
|
<link rel="icon" type="image/png" href="static/favicon.png"/>
|
||||||
|
<link rel="alternate" type="text/calendar" title="ICS" href="export.ics"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Dresden Ticker</h1>
|
<h1>Dresden Ticker</h1>
|
||||||
|
@ -62,6 +63,10 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<footer>
|
<footer>
|
||||||
|
<p>
|
||||||
|
Maschinenlesbarer Export:
|
||||||
|
<a href="export.rss">ICS</a>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Ein Projekt des
|
Ein Projekt des
|
||||||
<a href="https://www.c3d2.de/">C3D2</a>
|
<a href="https://www.c3d2.de/">C3D2</a>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user