diff options
-rw-r--r-- | src/database/file/mod.rs | 106 | ||||
-rw-r--r-- | src/frontend/style.css | 24 | ||||
-rw-r--r-- | src/frontend/templates/mod.rs | 11 |
3 files changed, 100 insertions, 41 deletions
diff --git a/src/database/file/mod.rs b/src/database/file/mod.rs index 67b3549..d67c920 100644 --- a/src/database/file/mod.rs +++ b/src/database/file/mod.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use futures::stream; use futures_util::StreamExt; use futures_util::TryStreamExt; +use serde_json::json; use crate::database::{ErrorKind, Result, Storage, StorageError, filter_post}; use fd_lock::RwLock; use log::debug; @@ -184,6 +185,34 @@ impl FileStorage { } } +async fn hydrate_author<S: Storage>(feed: &mut serde_json::Value, user: &'_ Option<String>, storage: &S) { + let url = feed["properties"]["uid"][0].as_str().unwrap(); + if let Some(author) = feed["properties"]["author"].clone().as_array() { + if !feed["type"].as_array().unwrap().iter().any(|i| i == "h-card") { + let author_list: Vec<serde_json::Value> = stream::iter(author.iter()) + .then(|i| async move { + if let Some(i) = i.as_str() { + match storage.get_post(i).await { + Ok(post) => match post { + Some(post) => match filter_post(post, user) { + Some(author) => author, + None => json!(i) + }, + None => json!(i) + }, + Err(e) => { + log::error!("Error while hydrating post {}: {}", url, e); + json!(i) + } + } + } else { i.clone() } + }) + .collect::<Vec<_>>().await; + feed["properties"].as_object_mut().unwrap()["author"] = json!(author_list); + } + } +} + #[async_trait] impl Storage for FileStorage { async fn post_exists(&self, url: &str) -> Result<bool> { @@ -386,50 +415,53 @@ impl Storage for FileStorage { limit: usize, user: &'a Option<String>, ) -> Result<Option<serde_json::Value>> { - match self.get_post(url).await? { - Some(feed) => match filter_post(feed, user) { - Some(mut feed) => { - if feed["children"].is_array() { - let children = feed["children"].as_array().unwrap().clone(); - let mut posts_iter = children.into_iter() - .map(|s: serde_json::Value| s.as_str().unwrap().to_string()); - if after.is_some() { - loop { - let i = posts_iter.next(); - if &i == after { - break; - } + if let Some(feed) = self.get_post(url).await? { + if let Some(mut feed) = filter_post(feed, user) { + if feed["children"].is_array() { + let children = feed["children"].as_array().unwrap().clone(); + let mut posts_iter = children.into_iter() + .map(|s: serde_json::Value| s.as_str().unwrap().to_string()); + if after.is_some() { + loop { + let i = posts_iter.next(); + if &i == after { + break; } } - let posts = stream::iter(posts_iter) - .map(|url: String| async move { - self.get_post(&url).await - }) - .buffered(std::cmp::min(3, limit)) - // Hack to unwrap the Option and sieve out broken links - // Broken links return None, and Stream::filter_map skips Nones. - .try_filter_map(|post: Option<serde_json::Value>| async move { Ok(post) }) - .try_filter_map(|post| async move { Ok(filter_post(post, user)) }) - .take(limit); - - match posts.try_collect::<Vec<serde_json::Value>>().await { - Ok(posts) => feed["children"] = serde_json::json!(posts), - Err(err) => { - return Err(StorageError::with_source( - ErrorKind::Other, "Feed assembly error", - Box::new(err) - )); - } + } + let posts = stream::iter(posts_iter) + .map(|url: String| async move { + self.get_post(&url).await + }) + .buffered(std::cmp::min(3, limit)) + // Hack to unwrap the Option and sieve out broken links + // Broken links return None, and Stream::filter_map skips Nones. + .try_filter_map(|post: Option<serde_json::Value>| async move { Ok(post) }) + .and_then(|mut post| async move { hydrate_author(&mut post, user, self).await; Ok(post) }) + .try_filter_map(|post| async move { Ok(filter_post(post, user)) }) + + .take(limit); + + match posts.try_collect::<Vec<serde_json::Value>>().await { + Ok(posts) => feed["children"] = serde_json::json!(posts), + Err(err) => { + return Err(StorageError::with_source( + ErrorKind::Other, "Feed assembly error", + Box::new(err) + )); } } - Ok(Some(feed)) - }, - None => Err(StorageError::new( + } + hydrate_author(&mut feed, user, self).await; + Ok(Some(feed)) + } else { + Err(StorageError::new( ErrorKind::PermissionDenied, "specified user cannot access this post", )) - }, - None => Ok(None) + } + } else { + Ok(None) } } diff --git a/src/frontend/style.css b/src/frontend/style.css index 1d6586b..a0add44 100644 --- a/src/frontend/style.css +++ b/src/frontend/style.css @@ -26,9 +26,9 @@ h1, .xxxlarge { margin-bottom: 0; font-size: 3.052rem; } -h2 .xxlarge {font-size: 2.441rem;} -h3 .xlarge {font-size: 1.953rem;} -h4 .larger {font-size: 1.563rem;} +h2, .xxlarge {font-size: 2.441rem;} +h3, .xlarge {font-size: 1.953rem;} +h4, .larger {font-size: 1.563rem;} h5, .large {font-size: 1.25rem;} h6, .normal {font-size: 1rem;} small, .small { font-size: 0.8em; } @@ -159,4 +159,20 @@ article.h-card img.u-photo { height: 8rem; border: 1px solid gray; margin-right: .75em; -} \ No newline at end of file +} + +.mini-h-card img { + height: 2em; + display: inline-block; + border: 2px solid gray; + border-radius: 100%; + margin-right: 0.5rem; +} + +.mini-h-card * { + vertical-align: middle; +} + +.mini-h-card a { + text-decoration: none; +} diff --git a/src/frontend/templates/mod.rs b/src/frontend/templates/mod.rs index a7c01e0..3585804 100644 --- a/src/frontend/templates/mod.rs +++ b/src/frontend/templates/mod.rs @@ -68,6 +68,17 @@ markup::define! { h1."p-name" { @post["properties"]["name"][0].as_str().unwrap() } + } else { + @if post["properties"]["author"][0].is_object() { + section."mini-h-card" { + a.larger[href=post["properties"]["author"][0]["properties"]["uid"][0].as_str().unwrap()] { + @if post["properties"]["author"][0]["properties"]["photo"][0].is_string() { + img[src=post["properties"]["author"][0]["properties"]["photo"][0].as_str().unwrap()] {} + } + @post["properties"]["author"][0]["properties"]["name"][0].as_str().unwrap() + } + } + } } div { span { |