diff options
author | Colin Reeder <colin@vpzom.click> | 2020-06-29 23:34:28 -0600 |
---|---|---|
committer | Colin Reeder <colin@vpzom.click> | 2020-06-29 23:34:28 -0600 |
commit | 34126a8421eab043202b693fb55df13d3334a8f7 (patch) | |
tree | 6234e84786a5ef9e9268cf8f87b8fe73eef41716 | |
parent | 1c9e1a95cfdd1394724f4edb48a5598438e0d163 (diff) |
Split off more modules
-rw-r--r-- | src/components/mod.rs | 238 | ||||
-rw-r--r-- | src/main.rs | 9 | ||||
-rw-r--r-- | src/resp_types.rs | 69 | ||||
-rw-r--r-- | src/routes/communities.rs | 5 | ||||
-rw-r--r-- | src/routes/mod.rs | 330 | ||||
-rw-r--r-- | src/routes/posts.rs | 8 | ||||
-rw-r--r-- | src/util.rs | 27 |
7 files changed, 355 insertions, 331 deletions
diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..d60282d --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,238 @@ +use std::borrow::Cow; + +use crate::resp_types::{ + RespMinimalAuthorInfo, RespMinimalCommunityInfo, RespPostCommentInfo, RespPostListPost, +}; +use crate::util::{abbreviate_link, author_is_me}; +use crate::PageBaseData; + +#[render::component] +pub fn Comment<'comment, 'base_data>( + comment: &'comment RespPostCommentInfo<'comment>, + base_data: &'base_data PageBaseData, +) { + render::rsx! { + <li> + <small><cite><UserLink user={comment.author.as_ref()} /></cite>{":"}</small> + <Content src={comment} /> + <div class={"actionList"}> + { + if base_data.login.is_some() { + Some(render::rsx! { + <> + <form method={"POST"} action={format!("/comments/{}/like", comment.id)} style={"display: inline"}> + <button r#type={"submit"}>{"Like"}</button> + </form> + <a href={format!("/comments/{}", comment.id)}>{"reply"}</a> + </> + }) + } else { + None + } + } + { + if author_is_me(&comment.author, &base_data.login) { + Some(render::rsx! { + <a href={format!("/comments/{}/delete", comment.id)}>{"delete"}</a> + }) + } else { + None + } + } + </div> + + { + match &comment.replies { + Some(replies) => { + Some(render::rsx! { + <ul> + { + replies.iter().map(|reply| { + render::rsx! { + <Comment comment={reply} base_data /> + } + }) + .collect::<Vec<_>>() + } + </ul> + }) + }, + None => None, + } + } + </li> + } +} + +pub struct CommunityLink<'community> { + pub community: &'community RespMinimalCommunityInfo<'community>, +} +impl<'community> render::Render for CommunityLink<'community> { + fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { + let community = &self.community; + + let href = format!("/communities/{}", community.id); + (render::rsx! { + <a href={&href}> + { + (if community.local { + community.name.as_ref().into() + } else { + Cow::Owned(format!("{}@{}", community.name, community.host)) + }).as_ref() + } + </a> + }) + .render_into(writer) + } +} + +pub trait HavingContent { + fn content_text(&self) -> Option<&str>; + fn content_html(&self) -> Option<&str>; +} + +impl<'a> HavingContent for RespPostCommentInfo<'a> { + fn content_text(&self) -> Option<&str> { + self.content_text.as_deref() + } + fn content_html(&self) -> Option<&str> { + self.content_html.as_deref() + } +} + +impl<'a> HavingContent for RespPostListPost<'a> { + fn content_text(&self) -> Option<&str> { + self.content_text.as_deref() + } + fn content_html(&self) -> Option<&str> { + self.content_html.as_deref() + } +} + +pub struct Content<'a, T: HavingContent + 'a> { + pub src: &'a T, +} + +impl<'a, T: HavingContent + 'a> render::Render for Content<'a, T> { + fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { + match self.src.content_html() { + Some(html) => { + let cleaned = ammonia::clean(&html); + writer.write_str("<p>")?; + render::raw!(cleaned.as_ref()).render_into(writer)?; + writer.write_str("</p>")?; + } + None => match self.src.content_text() { + Some(text) => { + writer.write_str("<p>")?; + text.render_into(writer)?; + writer.write_str("</p>")?; + } + None => {} + }, + } + + Ok(()) + } +} + +#[render::component] +pub fn HTPage<'base_data, Children: render::Render>( + base_data: &'base_data PageBaseData, + children: Children, +) { + render::rsx! { + <> + <render::html::HTML5Doctype /> + <html> + <head> + <meta charset={"utf-8"} /> + <link rel={"stylesheet"} href={"/static/main.css"} /> + </head> + <body> + <header class={"mainHeader"}> + <div class={"left actionList"}> + <a href={"/"} class={"siteName"}>{"lotide"}</a> + <a href={"/communities"}>{"Communities"}</a> + </div> + <div class={"right actionList"}> + { + match base_data.login { + Some(_) => None, + None => { + Some(render::rsx! { + <a href={"/login"}>{"Login"}</a> + }) + } + } + } + </div> + </header> + {children} + </body> + </html> + </> + } +} + +#[render::component] +pub fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool) { + render::rsx! { + <li> + <a href={format!("/posts/{}", post.id)}> + {post.title.as_ref()} + </a> + { + if let Some(href) = &post.href { + Some(render::rsx! { + <> + {" "} + <em><a href={href.as_ref()}>{abbreviate_link(&href)}{" ↗"}</a></em> + </> + }) + } else { + None + } + } + <br /> + {"Submitted by "}<UserLink user={post.author.as_ref()} /> + { + if !in_community { + Some(render::rsx! { + <>{" to "}<CommunityLink community={&post.community} /></> + }) + } else { + None + } + } + </li> + } +} + +pub struct UserLink<'user> { + pub user: Option<&'user RespMinimalAuthorInfo<'user>>, +} + +impl<'user> render::Render for UserLink<'user> { + fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { + match self.user { + None => "[unknown]".render_into(writer), + Some(user) => { + let href = format!("/users/{}", user.id); + (render::rsx! { + <a href={&href}> + { + (if user.local { + user.username.as_ref().into() + } else { + Cow::Owned(format!("{}@{}", user.username, user.host)) + }).as_ref() + } + </a> + }) + .render_into(writer) + } + } + } +} diff --git a/src/main.rs b/src/main.rs index b4f142b..7dc0567 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ #![feature(proc_macro_hygiene)] #![allow(unused_braces)] +use crate::resp_types::RespLoginInfo; use std::sync::Arc; use trout::hyper::RoutingFailureExtHyper; +mod components; +mod resp_types; mod routes; +mod util; pub type HttpClient = hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>; @@ -36,6 +40,11 @@ impl<T: 'static + std::error::Error + Send> From<T> for Error { } } +#[derive(Debug)] +pub struct PageBaseData { + pub login: Option<RespLoginInfo>, +} + pub fn simple_response( code: hyper::StatusCode, text: impl Into<hyper::Body>, diff --git a/src/resp_types.rs b/src/resp_types.rs new file mode 100644 index 0000000..e8858d5 --- /dev/null +++ b/src/resp_types.rs @@ -0,0 +1,69 @@ +use serde_derive::Deserialize; +use std::borrow::Cow; + +#[derive(Deserialize, Debug)] +pub struct RespMinimalAuthorInfo<'a> { + pub id: i64, + pub username: Cow<'a, str>, + pub local: bool, + pub host: Cow<'a, str>, +} + +#[derive(Deserialize, Debug)] +pub struct RespPostListPost<'a> { + pub id: i64, + pub title: Cow<'a, str>, + pub href: Option<Cow<'a, str>>, + pub content_text: Option<Cow<'a, str>>, + pub content_html: Option<Cow<'a, str>>, + #[serde(borrow)] + pub author: Option<RespMinimalAuthorInfo<'a>>, + pub created: Cow<'a, str>, + #[serde(borrow)] + pub community: RespMinimalCommunityInfo<'a>, +} + +#[derive(Deserialize, Debug)] +pub struct RespPostCommentInfo<'a> { + pub id: i64, + #[serde(borrow)] + pub author: Option<RespMinimalAuthorInfo<'a>>, + pub created: Cow<'a, str>, + pub content_text: Option<Cow<'a, str>>, + pub content_html: Option<Cow<'a, str>>, + #[serde(borrow)] + pub replies: Option<Vec<RespPostCommentInfo<'a>>>, +} + +#[derive(Deserialize, Debug)] +pub struct RespPostInfo<'a> { + #[serde(flatten, borrow)] + pub base: RespPostListPost<'a>, + pub score: i64, + #[serde(borrow)] + pub comments: Vec<RespPostCommentInfo<'a>>, +} + +impl<'a> AsRef<RespPostListPost<'a>> for RespPostInfo<'a> { + fn as_ref(&self) -> &RespPostListPost<'a> { + &self.base + } +} + +#[derive(Deserialize, Debug)] +pub struct RespMinimalCommunityInfo<'a> { + pub id: i64, + pub name: Cow<'a, str>, + pub local: bool, + pub host: Cow<'a, str>, +} + +#[derive(Deserialize, Debug)] +pub struct RespLoginInfoUser { + pub id: i64, +} + +#[derive(Deserialize, Debug)] +pub struct RespLoginInfo { + pub user: RespLoginInfoUser, +} diff --git a/src/routes/communities.rs b/src/routes/communities.rs index 886a21f..9240be5 100644 --- a/src/routes/communities.rs +++ b/src/routes/communities.rs @@ -1,6 +1,7 @@ +use crate::components::{CommunityLink, HTPage, PostItem}; +use crate::resp_types::{RespMinimalCommunityInfo, RespPostListPost}; use crate::routes::{ - fetch_base_data, get_cookie_map, get_cookie_map_for_req, html_response, res_to_error, - with_auth, CommunityLink, HTPage, PostItem, RespMinimalCommunityInfo, RespPostListPost, + fetch_base_data, get_cookie_map, get_cookie_map_for_req, html_response, res_to_error, with_auth, }; use std::collections::HashMap; use std::sync::Arc; diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6e3a7d8..06f5448 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,6 +2,10 @@ use serde_derive::Deserialize; use std::borrow::Cow; use std::sync::Arc; +use crate::components::{Content, HTPage, PostItem, UserLink}; +use crate::resp_types::{RespPostCommentInfo, RespPostListPost}; +use crate::PageBaseData; + mod communities; mod posts; mod r#static; @@ -54,32 +58,6 @@ fn with_auth( Ok(new_req) } -fn author_is_me(author: &Option<RespMinimalAuthorInfo<'_>>, login: &Option<RespLoginInfo>) -> bool { - if let Some(author) = author { - if let Some(login) = login { - if author.id == login.user.id { - return true; - } - } - } - false -} - -#[derive(Deserialize, Debug)] -struct RespLoginInfoUser { - id: i64, -} - -#[derive(Deserialize, Debug)] -struct RespLoginInfo { - user: RespLoginInfoUser, -} - -#[derive(Debug)] -struct PageBaseData { - login: Option<RespLoginInfo>, -} - async fn fetch_base_data( backend_host: &str, http_client: &crate::HttpClient, @@ -106,306 +84,6 @@ async fn fetch_base_data( Ok(PageBaseData { login }) } -#[derive(Deserialize, Debug)] -struct RespMinimalAuthorInfo<'a> { - id: i64, - username: Cow<'a, str>, - local: bool, - host: Cow<'a, str>, -} - -trait HavingContent { - fn content_text(&self) -> Option<&str>; - fn content_html(&self) -> Option<&str>; -} - -#[derive(Deserialize, Debug)] -struct RespPostListPost<'a> { - id: i64, - title: Cow<'a, str>, - href: Option<Cow<'a, str>>, - content_text: Option<Cow<'a, str>>, - content_html: Option<Cow<'a, str>>, - #[serde(borrow)] - author: Option<RespMinimalAuthorInfo<'a>>, - created: Cow<'a, str>, - #[serde(borrow)] - community: RespMinimalCommunityInfo<'a>, -} - -impl<'a> HavingContent for RespPostListPost<'a> { - fn content_text(&self) -> Option<&str> { - self.content_text.as_deref() - } - fn content_html(&self) -> Option<&str> { - self.content_html.as_deref() - } -} - -#[derive(Deserialize, Debug)] -struct RespPostCommentInfo<'a> { - id: i64, - #[serde(borrow)] - author: Option<RespMinimalAuthorInfo<'a>>, - created: Cow<'a, str>, - content_text: Option<Cow<'a, str>>, - content_html: Option<Cow<'a, str>>, - #[serde(borrow)] - replies: Option<Vec<RespPostCommentInfo<'a>>>, -} - -impl<'a> HavingContent for RespPostCommentInfo<'a> { - fn content_text(&self) -> Option<&str> { - self.content_text.as_deref() - } - fn content_html(&self) -> Option<&str> { - self.content_html.as_deref() - } -} - -#[derive(Deserialize, Debug)] -struct RespPostInfo<'a> { - #[serde(flatten, borrow)] - pub base: RespPostListPost<'a>, - pub score: i64, - #[serde(borrow)] - pub comments: Vec<RespPostCommentInfo<'a>>, -} - -impl<'a> AsRef<RespPostListPost<'a>> for RespPostInfo<'a> { - fn as_ref(&self) -> &RespPostListPost<'a> { - &self.base - } -} - -#[render::component] -fn HTPage<'base_data, Children: render::Render>( - base_data: &'base_data PageBaseData, - children: Children, -) { - render::rsx! { - <> - <render::html::HTML5Doctype /> - <html> - <head> - <meta charset={"utf-8"} /> - <link rel={"stylesheet"} href={"/static/main.css"} /> - </head> - <body> - <header class={"mainHeader"}> - <div class={"left actionList"}> - <a href={"/"} class={"siteName"}>{"lotide"}</a> - <a href={"/communities"}>{"Communities"}</a> - </div> - <div class={"right actionList"}> - { - match base_data.login { - Some(_) => None, - None => { - Some(render::rsx! { - <a href={"/login"}>{"Login"}</a> - }) - } - } - } - </div> - </header> - {children} - </body> - </html> - </> - } -} - -fn abbreviate_link(href: &str) -> &str { - // Attempt to find the hostname from the URL - match href.find("://") { - Some(idx1) => match href[(idx1 + 3)..].find('/') { - Some(idx2) => Some(&href[(idx1 + 3)..(idx1 + 3 + idx2)]), - None => None, - }, - None => None, - } - .unwrap_or(href) -} - -#[render::component] -fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool) { - render::rsx! { - <li> - <a href={format!("/posts/{}", post.id)}> - {post.title.as_ref()} - </a> - { - if let Some(href) = &post.href { - Some(render::rsx! { - <> - {" "} - <em><a href={href.as_ref()}>{abbreviate_link(&href)}{" ↗"}</a></em> - </> - }) - } else { - None - } - } - <br /> - {"Submitted by "}<UserLink user={post.author.as_ref()} /> - { - if !in_community { - Some(render::rsx! { - <>{" to "}<CommunityLink community={&post.community} /></> - }) - } else { - None - } - } - </li> - } -} - -#[derive(Deserialize, Debug)] -struct RespMinimalCommunityInfo<'a> { - id: i64, - name: Cow<'a, str>, - local: bool, - host: Cow<'a, str>, -} - -struct UserLink<'user> { - user: Option<&'user RespMinimalAuthorInfo<'user>>, -} - -impl<'user> render::Render for UserLink<'user> { - fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { - match self.user { - None => "[unknown]".render_into(writer), - Some(user) => { - let href = format!("/users/{}", user.id); - (render::rsx! { - <a href={&href}> - { - (if user.local { - user.username.as_ref().into() - } else { - Cow::Owned(format!("{}@{}", user.username, user.host)) - }).as_ref() - } - </a> - }) - .render_into(writer) - } - } - } -} - -struct CommunityLink<'community> { - community: &'community RespMinimalCommunityInfo<'community>, -} -impl<'community> render::Render for CommunityLink<'community> { - fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { - let community = &self.community; - - let href = format!("/communities/{}", community.id); - (render::rsx! { - <a href={&href}> - { - (if community.local { - community.name.as_ref().into() - } else { - Cow::Owned(format!("{}@{}", community.name, community.host)) - }).as_ref() - } - </a> - }) - .render_into(writer) - } -} - -struct Content<'a, T: HavingContent + 'a> { - src: &'a T, -} - -impl<'a, T: HavingContent + 'a> render::Render for Content<'a, T> { - fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { - match self.src.content_html() { - Some(html) => { - let cleaned = ammonia::clean(&html); - writer.write_str("<p>")?; - render::raw!(cleaned.as_ref()).render_into(writer)?; - writer.write_str("</p>")?; - } - None => match self.src.content_text() { - Some(text) => { - writer.write_str("<p>")?; - text.render_into(writer)?; - writer.write_str("</p>")?; - } - None => {} - }, - } - - Ok(()) - } -} - -#[render::component] -fn Comment<'comment, 'base_data>( - comment: &'comment RespPostCommentInfo<'comment>, - base_data: &'base_data PageBaseData, -) { - render::rsx! { - <li> - <small><cite><UserLink user={comment.author.as_ref()} /></cite>{":"}</small> - <Content src={comment} /> - <div class={"actionList"}> - { - if base_data.login.is_some() { - Some(render::rsx! { - <> - <form method={"POST"} action={format!("/comments/{}/like", comment.id)} style={"display: inline"}> - <button r#type={"submit"}>{"Like"}</button> - </form> - <a href={format!("/comments/{}", comment.id)}>{"reply"}</a> - </> - }) - } else { - None - } - } - { - if author_is_me(&comment.author, &base_data.login) { - Some(render::rsx! { - <a href={format!("/comments/{}/delete", comment.id)}>{"delete"}</a> - }) - } else { - None - } - } - </div> - - { - match &comment.replies { - Some(replies) => { - Some(render::rsx! { - <ul> - { - replies.iter().map(|reply| { - render::rsx! { - <Comment comment={reply} base_data /> - } - }) - .collect::<Vec<_>>() - } - </ul> - }) - }, - None => None, - } - } - </li> - } -} - fn html_response(html: String) -> hyper::Response<hyper::Body> { let mut res = hyper::Response::new(html.into()); res.headers_mut().insert( diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 8efde41..3137f0e 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,8 +1,10 @@ use super::{ - author_is_me, fetch_base_data, get_cookie_map, get_cookie_map_for_req, get_cookies_string, - html_response, res_to_error, with_auth, Comment, CommunityLink, Content, HTPage, RespPostInfo, - UserLink, + fetch_base_data, get_cookie_map, get_cookie_map_for_req, get_cookies_string, html_response, + res_to_error, with_auth, }; +use crate::components::{Comment, CommunityLink, Content, HTPage, UserLink}; +use crate::resp_types::RespPostInfo; +use crate::util::author_is_me; use std::sync::Arc; async fn page_post( diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..4724091 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,27 @@ +use crate::resp_types::{RespLoginInfo, RespMinimalAuthorInfo}; + +pub fn abbreviate_link(href: &str) -> &str { + // Attempt to find the hostname from the URL + match href.find("://") { + Some(idx1) => match href[(idx1 + 3)..].find('/') { + Some(idx2) => Some(&href[(idx1 + 3)..(idx1 + 3 + idx2)]), + None => None, + }, + None => None, + } + .unwrap_or(href) +} + +pub fn author_is_me( + author: &Option<RespMinimalAuthorInfo<'_>>, + login: &Option<RespLoginInfo>, +) -> bool { + if let Some(author) = author { + if let Some(login) = login { + if author.id == login.user.id { + return true; + } + } + } + false +} |