rss and atom feeds
This commit is contained in:
@@ -0,0 +1,158 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::extract::{Query, State};
|
||||||
|
use axum::http::header;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct FeedQuery {
|
||||||
|
pub preset: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn atom_feed(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Query(q): Query<FeedQuery>,
|
||||||
|
) -> Response {
|
||||||
|
let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
|
||||||
|
let feed = build_atom(&entries);
|
||||||
|
([(header::CONTENT_TYPE, "application/atom+xml; charset=utf-8")], feed).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rss_feed(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Query(q): Query<FeedQuery>,
|
||||||
|
) -> Response {
|
||||||
|
let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
|
||||||
|
let feed = build_rss(&entries);
|
||||||
|
([(header::CONTENT_TYPE, "application/rss+xml; charset=utf-8")], feed).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn json_feed(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Query(q): Query<FeedQuery>,
|
||||||
|
) -> Response {
|
||||||
|
let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
|
||||||
|
let feed = build_json_feed(&entries);
|
||||||
|
([(header::CONTENT_TYPE, "application/feed+json; charset=utf-8")], feed).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FeedEntry {
|
||||||
|
date: String,
|
||||||
|
preset: String,
|
||||||
|
short_id: String,
|
||||||
|
width: i64,
|
||||||
|
height: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_recent_dailies(state: &AppState, preset_filter: Option<&str>) -> Vec<FeedEntry> {
|
||||||
|
let dbi = state.db.lock().await;
|
||||||
|
let (query, param): (String, Option<String>) = if let Some(preset) = preset_filter {
|
||||||
|
("SELECT daily_date, preset, short_id, width, height FROM puzzles WHERE daily_date IS NOT NULL AND preset = ?1 ORDER BY daily_date DESC LIMIT 14".into(), Some(preset.to_string()))
|
||||||
|
} else {
|
||||||
|
("SELECT daily_date, preset, short_id, width, height FROM puzzles WHERE daily_date IS NOT NULL ORDER BY daily_date DESC LIMIT 28".into(), None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stmt = dbi.db.prepare(&query).unwrap();
|
||||||
|
let rows = if let Some(p) = ¶m {
|
||||||
|
stmt.query_map(rusqlite::params![p], |row| {
|
||||||
|
Ok(FeedEntry {
|
||||||
|
date: row.get::<_, String>(0)?,
|
||||||
|
preset: row.get::<_, String>(1)?,
|
||||||
|
short_id: row.get::<_, String>(2)?,
|
||||||
|
width: row.get::<_, i64>(3)?,
|
||||||
|
height: row.get::<_, i64>(4)?,
|
||||||
|
})
|
||||||
|
}).unwrap().filter_map(|r| r.ok()).collect()
|
||||||
|
} else {
|
||||||
|
stmt.query_map([], |row| {
|
||||||
|
Ok(FeedEntry {
|
||||||
|
date: row.get::<_, String>(0)?,
|
||||||
|
preset: row.get::<_, String>(1)?,
|
||||||
|
short_id: row.get::<_, String>(2)?,
|
||||||
|
width: row.get::<_, i64>(3)?,
|
||||||
|
height: row.get::<_, i64>(4)?,
|
||||||
|
})
|
||||||
|
}).unwrap().filter_map(|r| r.ok()).collect()
|
||||||
|
};
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_atom(entries: &[FeedEntry]) -> String {
|
||||||
|
let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title>cruciverb daily puzzles</title>
|
||||||
|
<id>urn:cruciverb:daily</id>
|
||||||
|
<updated>"#);
|
||||||
|
|
||||||
|
if let Some(e) = entries.first() {
|
||||||
|
xml.push_str(&format!("{}T00:00:00Z", e.date));
|
||||||
|
}
|
||||||
|
xml.push_str("</updated>\n");
|
||||||
|
|
||||||
|
for e in entries {
|
||||||
|
xml.push_str(&format!(
|
||||||
|
r#" <entry>
|
||||||
|
<title>cruciverb {} - {} - {}x{}</title>
|
||||||
|
<id>urn:cruciverb:{}-{}</id>
|
||||||
|
<link href="/p/{}" />
|
||||||
|
<published>{}T00:00:00Z</published>
|
||||||
|
<summary>{}x{} {} crossword puzzle</summary>
|
||||||
|
</entry>
|
||||||
|
"#,
|
||||||
|
e.preset, e.date, e.width, e.height,
|
||||||
|
e.preset, e.date,
|
||||||
|
e.short_id,
|
||||||
|
e.date,
|
||||||
|
e.width, e.height, e.preset,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
xml.push_str("</feed>\n");
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_rss(entries: &[FeedEntry]) -> String {
|
||||||
|
let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>cruciverb daily puzzles</title>
|
||||||
|
<description>daily crossword puzzles</description>
|
||||||
|
"#);
|
||||||
|
|
||||||
|
for e in entries {
|
||||||
|
xml.push_str(&format!(
|
||||||
|
r#" <item>
|
||||||
|
<title>cruciverb {} - {} - {}x{}</title>
|
||||||
|
<link>/p/{}</link>
|
||||||
|
<guid>cruciverb-{}-{}</guid>
|
||||||
|
<description>{}x{} {} crossword puzzle</description>
|
||||||
|
</item>
|
||||||
|
"#,
|
||||||
|
e.preset, e.date, e.width, e.height,
|
||||||
|
e.short_id,
|
||||||
|
e.preset, e.date,
|
||||||
|
e.width, e.height, e.preset,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
xml.push_str("</channel>\n</rss>\n");
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_json_feed(entries: &[FeedEntry]) -> String {
|
||||||
|
let items: Vec<serde_json::Value> = entries.iter().map(|e| {
|
||||||
|
serde_json::json!({
|
||||||
|
"id": format!("cruciverb-{}-{}", e.preset, e.date),
|
||||||
|
"title": format!("cruciverb {} - {} - {}x{}", e.preset, e.date, e.width, e.height),
|
||||||
|
"url": format!("/p/{}", e.short_id),
|
||||||
|
"content_text": format!("{}x{} {} crossword puzzle", e.width, e.height, e.preset),
|
||||||
|
"date_published": format!("{}T00:00:00Z", e.date),
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
serde_json::json!({
|
||||||
|
"version": "https://jsonfeed.org/version/1.1",
|
||||||
|
"title": "cruciverb daily puzzles",
|
||||||
|
"items": items,
|
||||||
|
}).to_string()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user