Convert md to html for feeds, try to deduplicate code

This commit is contained in:
Felix 2020-03-28 16:56:20 +01:00
parent ac6dc65342
commit bafc2fc7ac
3 changed files with 100 additions and 110 deletions

11
server/Cargo.lock generated vendored
View file

@ -1,5 +1,14 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]]
name = "Markdown-to-HTML-rs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "activitypub" name = "activitypub"
version = "0.2.0" version = "0.2.0"
@ -1369,6 +1378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "lemmy_server" name = "lemmy_server"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"Markdown-to-HTML-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2823,6 +2833,7 @@ dependencies = [
] ]
[metadata] [metadata]
"checksum Markdown-to-HTML-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1a9bda9d68f643d9b63888996896ce5be873d0f22fe1c859bce84dd4bd4661b"
"checksum activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d538a21b137ec0f63cc579ef4afa4ab13aa85b4f8af15a033683edd97c50718d" "checksum activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d538a21b137ec0f63cc579ef4afa4ab13aa85b4f8af15a033683edd97c50718d"
"checksum activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65608fdeae5eb05485d5b71a3d2242d76b2b7413608c196d47eb4dff3eed7b85" "checksum activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65608fdeae5eb05485d5b71a3d2242d76b2b7413608c196d47eb4dff3eed7b85"
"checksum activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c2a3958d240f40eff1f31b5f679a6e0d4ce2a16812886a3ec0164f3a2ca517" "checksum activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c2a3958d240f40eff1f31b5f679a6e0d4ce2a16812886a3ec0164f3a2ca517"

1
server/Cargo.toml vendored
View file

@ -36,3 +36,4 @@ config = "0.10.1"
hjson = "0.8.2" hjson = "0.8.2"
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
isahc = "0.9" isahc = "0.9"
Markdown-to-HTML-rs = "0.1.0"

View file

@ -10,7 +10,7 @@ use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView};
use crate::db::{ListingType, SortType}; use crate::db::{ListingType, SortType};
use crate::Settings; use crate::Settings;
use actix_web::{web, HttpResponse, Result}; use actix_web::{web, HttpResponse, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
@ -18,6 +18,7 @@ use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
use serde::Deserialize; use serde::Deserialize;
use std::str::FromStr; use std::str::FromStr;
use strum::ParseError; use strum::ParseError;
extern crate Markdown_to_HTML_rs;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Params { pub struct Params {
@ -34,7 +35,6 @@ enum RequestType {
pub fn config(cfg: &mut web::ServiceConfig) { pub fn config(cfg: &mut web::ServiceConfig) {
cfg cfg
.route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed))
.route("/feeds/all.xml", web::get().to(feeds::get_all_feed))
.route("/feeds/all.xml", web::get().to(feeds::get_all_feed)); .route("/feeds/all.xml", web::get().to(feeds::get_all_feed));
} }
@ -44,9 +44,7 @@ async fn get_all_feed(
) -> Result<HttpResponse, actix_web::Error> { ) -> Result<HttpResponse, actix_web::Error> {
let res = web::block(move || { let res = web::block(move || {
let conn = db.get()?; let conn = db.get()?;
get_feed_all_data(&conn, &get_sort_type(info)?)
let sort_type = get_sort_type(info)?;
get_feed_all_data(&conn, &sort_type)
}) })
.await .await
.map(|rss| { .map(|rss| {
@ -58,6 +56,29 @@ async fn get_all_feed(
Ok(res) Ok(res)
} }
fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, failure::Error> {
let site_view = SiteView::read(&conn)?;
let posts = PostQueryBuilder::create(&conn)
.listing_type(ListingType::All)
.sort(sort_type)
.list()?;
let items = create_post_items(posts);
let mut channel_builder = ChannelBuilder::default();
channel_builder
.title(&format!("{} - All", site_view.name))
.link(format!("https://{}", Settings::get().hostname))
.items(items);
if let Some(site_desc) = site_view.description {
channel_builder.description(&site_desc);
}
Ok(channel_builder.build().unwrap().to_string())
}
async fn get_feed( async fn get_feed(
path: web::Path<(String, String)>, path: web::Path<(String, String)>,
info: web::Query<Params>, info: web::Query<Params>,
@ -86,6 +107,7 @@ async fn get_feed(
} }
}) })
.await .await
.map(|builder| builder.build().unwrap().to_string())
.map(|rss| { .map(|rss| {
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/rss+xml") .content_type("application/rss+xml")
@ -103,34 +125,11 @@ fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
SortType::from_str(&sort_query) SortType::from_str(&sort_query)
} }
fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, failure::Error> {
let site_view = SiteView::read(&conn)?;
let posts = PostQueryBuilder::create(&conn)
.listing_type(ListingType::All)
.sort(sort_type)
.list()?;
let items = create_post_items(posts);
let mut channel_builder = ChannelBuilder::default();
channel_builder
.title(&format!("{} - All", site_view.name))
.link(format!("https://{}", Settings::get().hostname))
.items(items);
if let Some(site_desc) = site_view.description {
channel_builder.description(&site_desc);
}
Ok(channel_builder.build().unwrap().to_string())
}
fn get_feed_user( fn get_feed_user(
conn: &PgConnection, conn: &PgConnection,
sort_type: &SortType, sort_type: &SortType,
user_name: String, user_name: String,
) -> Result<String, Error> { ) -> Result<ChannelBuilder, Error> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user = User_::find_by_username(&conn, &user_name)?; let user = User_::find_by_username(&conn, &user_name)?;
let user_url = user.get_profile_url(); let user_url = user.get_profile_url();
@ -149,14 +148,14 @@ fn get_feed_user(
.link(user_url) .link(user_url)
.items(items); .items(items);
Ok(channel_builder.build().unwrap().to_string()) Ok(channel_builder)
} }
fn get_feed_community( fn get_feed_community(
conn: &PgConnection, conn: &PgConnection,
sort_type: &SortType, sort_type: &SortType,
community_name: String, community_name: String,
) -> Result<String, Error> { ) -> Result<ChannelBuilder, Error> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let community = Community::read_from_name(&conn, community_name)?; let community = Community::read_from_name(&conn, community_name)?;
let community_url = community.get_url(); let community_url = community.get_url();
@ -179,10 +178,14 @@ fn get_feed_community(
channel_builder.description(&community_desc); channel_builder.description(&community_desc);
} }
Ok(channel_builder.build().unwrap().to_string()) Ok(channel_builder)
} }
fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Result<String, Error> { fn get_feed_front(
conn: &PgConnection,
sort_type: &SortType,
jwt: String,
) -> Result<ChannelBuilder, Error> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user_id = Claims::decode(&jwt)?.claims.id; let user_id = Claims::decode(&jwt)?.claims.id;
@ -204,10 +207,10 @@ fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Res
channel_builder.description(&site_desc); channel_builder.description(&site_desc);
} }
Ok(channel_builder.build().unwrap().to_string()) Ok(channel_builder)
} }
fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<String, Error> { fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Error> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user_id = Claims::decode(&jwt)?.claims.id; let user_id = Claims::decode(&jwt)?.claims.id;
@ -233,86 +236,61 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<String, Error> {
channel_builder.description(&site_desc); channel_builder.description(&site_desc);
} }
Ok(channel_builder.build().unwrap().to_string()) Ok(channel_builder)
} }
fn create_reply_and_mention_items( fn create_reply_and_mention_items(
replies: Vec<ReplyView>, replies: Vec<ReplyView>,
mentions: Vec<UserMentionView>, mentions: Vec<UserMentionView>,
) -> Vec<Item> { ) -> Vec<Item> {
let mut items: Vec<Item> = Vec::new(); let mut reply_items: Vec<Item> = replies
.iter()
.map(|r| {
let reply_url = format!(
"https://{}/post/{}/comment/{}",
Settings::get().hostname,
r.post_id,
r.id
);
build_item(&r.creator_name, &r.published, &reply_url, &r.content)
})
.collect();
for r in replies { let mut mention_items: Vec<Item> = mentions
let mut i = ItemBuilder::default(); .iter()
.map(|m| {
let mention_url = format!(
"https://{}/post/{}/comment/{}",
Settings::get().hostname,
m.post_id,
m.id
);
build_item(&m.creator_name, &m.published, &mention_url, &m.content)
})
.collect();
i.title(format!("Reply from {}", r.creator_name)); reply_items.append(&mut mention_items);
reply_items
}
let author_url = format!("https://{}/u/{}", Settings::get().hostname, r.creator_name); fn build_item(creator_name: &str, published: &NaiveDateTime, url: &str, content: &str) -> Item {
i.author(format!( let mut i = ItemBuilder::default();
"/u/{} <a href=\"{}\">(link)</a>", i.title(format!("Reply from {}", creator_name));
r.creator_name, author_url let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name);
)); i.author(format!(
"/u/{} <a href=\"{}\">(link)</a>",
let dt = DateTime::<Utc>::from_utc(r.published, Utc); creator_name, author_url
i.pub_date(dt.to_rfc2822()); ));
let dt = DateTime::<Utc>::from_utc(*published, Utc);
let reply_url = format!( i.pub_date(dt.to_rfc2822());
"https://{}/post/{}/comment/{}", i.comments(url.to_owned());
Settings::get().hostname, let guid = GuidBuilder::default().permalink(true).value(url).build();
r.post_id, i.guid(guid.unwrap());
r.id i.link(url.to_owned());
); // TODO add images
i.comments(reply_url.to_owned()); let html = Markdown_to_HTML_rs::replace_all(&content.to_string());
let guid = GuidBuilder::default() i.description(html);
.permalink(true) i.build().unwrap()
.value(&reply_url)
.build();
i.guid(guid.unwrap());
i.link(reply_url);
// TODO find a markdown to html parser here, do images, etc
i.description(r.content);
items.push(i.build().unwrap());
}
for m in mentions {
let mut i = ItemBuilder::default();
i.title(format!("Mention from {}", m.creator_name));
let author_url = format!("https://{}/u/{}", Settings::get().hostname, m.creator_name);
i.author(format!(
"/u/{} <a href=\"{}\">(link)</a>",
m.creator_name, author_url
));
let dt = DateTime::<Utc>::from_utc(m.published, Utc);
i.pub_date(dt.to_rfc2822());
let mention_url = format!(
"https://{}/post/{}/comment/{}",
Settings::get().hostname,
m.post_id,
m.id
);
i.comments(mention_url.to_owned());
let guid = GuidBuilder::default()
.permalink(true)
.value(&mention_url)
.build();
i.guid(guid.unwrap());
i.link(mention_url);
// TODO find a markdown to html parser here, do images, etc
i.description(m.content);
items.push(i.build().unwrap());
}
items
} }
fn create_post_items(posts: Vec<PostView>) -> Vec<Item> { fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
@ -359,9 +337,8 @@ fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
i.link(url); i.link(url);
} }
// TODO find a markdown to html parser here, do images, etc // TODO add images
let mut description = format!(" let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
author_url, author_url,
p.creator_name, p.creator_name,
community_url, community_url,
@ -371,7 +348,8 @@ fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
p.number_of_comments); p.number_of_comments);
if let Some(body) = p.body { if let Some(body) = p.body {
description.push_str(&format!("<br><br>{}", body)); let html = Markdown_to_HTML_rs::replace_all(&body);
description.push_str(&format!("<br><br>{}", html));
} }
i.description(description); i.description(description);