summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <colin@vpzom.click>2020-06-29 23:34:28 -0600
committerColin Reeder <colin@vpzom.click>2020-06-29 23:34:28 -0600
commit34126a8421eab043202b693fb55df13d3334a8f7 (patch)
tree6234e84786a5ef9e9268cf8f87b8fe73eef41716
parent1c9e1a95cfdd1394724f4edb48a5598438e0d163 (diff)
Split off more modules
-rw-r--r--src/components/mod.rs238
-rw-r--r--src/main.rs9
-rw-r--r--src/resp_types.rs69
-rw-r--r--src/routes/communities.rs5
-rw-r--r--src/routes/mod.rs330
-rw-r--r--src/routes/posts.rs8
-rw-r--r--src/util.rs27
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
+}