feat: add rss feed and remove rev feature
This commit is contained in:
parent
3937d8fd75
commit
5356e2dbb4
8 changed files with 268 additions and 13 deletions
|
|
@ -1,9 +1,12 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::http::header::CONTENT_TYPE;
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::Json;
|
||||
use base64::Engine;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
|
|
@ -25,6 +28,22 @@ pub struct QuestionStats {
|
|||
answered: i64,
|
||||
}
|
||||
|
||||
const SITE_URL: &str = "https://jetpham.com";
|
||||
|
||||
fn xml_escape(text: &str) -> String {
|
||||
text.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
}
|
||||
|
||||
fn rss_pub_date(timestamp: &str) -> String {
|
||||
DateTime::parse_from_rfc3339(timestamp)
|
||||
.map(|dt| dt.to_rfc2822())
|
||||
.unwrap_or_else(|_| Utc::now().to_rfc2822())
|
||||
}
|
||||
|
||||
pub async fn get_questions(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<Question>>, StatusCode> {
|
||||
|
|
@ -80,6 +99,63 @@ pub async fn get_question_stats(
|
|||
Ok(Json(QuestionStats { asked, answered }))
|
||||
}
|
||||
|
||||
pub async fn get_question_rss(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let db = state
|
||||
.db
|
||||
.lock()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let mut stmt = db
|
||||
.prepare(
|
||||
"SELECT id, question, answer, created_at, answered_at \
|
||||
FROM questions WHERE answer IS NOT NULL \
|
||||
ORDER BY answered_at DESC",
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let questions = stmt
|
||||
.query_map([], |row| {
|
||||
Ok(Question {
|
||||
id: row.get(0)?,
|
||||
question: row.get(1)?,
|
||||
answer: row.get(2)?,
|
||||
created_at: row.get(3)?,
|
||||
answered_at: row.get(4)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let items = questions
|
||||
.into_iter()
|
||||
.map(|question| {
|
||||
let guid = format!("{SITE_URL}/qa#question-{}", question.id);
|
||||
let description = format!(
|
||||
"Question: {}\n\nAnswer: {}",
|
||||
question.question, question.answer
|
||||
);
|
||||
|
||||
format!(
|
||||
"<item><title>{}</title><link>{}</link><guid>{}</guid><pubDate>{}</pubDate><description>{}</description></item>",
|
||||
xml_escape(&question.question),
|
||||
xml_escape(&guid),
|
||||
xml_escape(&guid),
|
||||
xml_escape(&rss_pub_date(&question.answered_at)),
|
||||
xml_escape(&description),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
let xml = format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\"><channel><title>Jet Pham Q+A</title><link>{SITE_URL}/qa</link><description>Answered questions from Jet Pham's site</description><language>en-us</language>{items}</channel></rss>"
|
||||
);
|
||||
|
||||
Ok(([(CONTENT_TYPE, "application/rss+xml; charset=utf-8")], xml))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubmitQuestion {
|
||||
question: String,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
get(handlers::get_questions).post(handlers::post_question),
|
||||
)
|
||||
.route("/api/questions/stats", get(handlers::get_question_stats))
|
||||
.route("/qa/rss.xml", get(handlers::get_question_rss))
|
||||
.route("/api/webhook", post(handlers::webhook))
|
||||
.layer(CorsLayer::permissive())
|
||||
.with_state(state);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue