hyaenidae/src/comments/node.rs
2021-04-02 12:07:19 -05:00

245 lines
7.5 KiB
Rust

use crate::{
comments::{can_view_comment_logged_out_no_recurse, can_view_comment_no_recurse},
error::{Error, OptionExt},
extensions::{CommentExt, ProfileExt, SubmissionExt},
views::OwnedProfileView,
State,
};
use hyaenidae_profiles::store::{Comment, File, Profile, Submission};
use hyaenidae_toolkit::Link;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Default)]
pub struct Cache {
pub(crate) comments: HashMap<Uuid, Comment>,
pub(crate) submissions: HashMap<Uuid, Submission>,
pub(crate) profiles: HashMap<Uuid, Profile>,
pub(crate) files: HashMap<Uuid, File>,
}
impl Cache {
pub(crate) fn new() -> Self {
Cache::default()
}
}
#[derive(Clone, Copy, Debug)]
pub enum Item {
Submission(Uuid),
Comment(Uuid),
}
#[derive(Debug)]
pub struct CommentNode {
pub(crate) item: Item,
pub(crate) author_id: Uuid,
pub(crate) is_self: bool,
pub(crate) children: Vec<CommentNode>,
}
impl CommentNode {
pub(crate) fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub(crate) fn name(&self, cache: &Cache) -> Option<String> {
cache.profiles.get(&self.author_id).map(|p| p.name())
}
pub(crate) fn view_link(&self, cache: &Cache) -> Option<Link> {
match self.item {
Item::Submission(id) => {
let submission = cache.submissions.get(&id)?;
Some(Link::current_tab(&submission.view_path()).plain(true))
}
Item::Comment(id) => {
let comment = cache.comments.get(&id)?;
Some(Link::current_tab(&comment.view_path()).plain(true))
}
}
}
pub(crate) fn edit_link(&self, cache: &Cache) -> Option<Link> {
if self.is_self {
if let Some(comment) = self.comment(cache) {
return Some(Link::current_tab(&comment.edit_path()).plain(true));
}
}
None
}
pub(crate) fn author(&self, cache: &Cache) -> OwnedProfileView {
let profile = cache.profiles.get(&self.author_id).unwrap().clone();
OwnedProfileView {
banner: profile
.banner()
.and_then(|banner| cache.files.get(&banner))
.map(|banner| banner.clone()),
icon: profile
.icon()
.and_then(|icon| cache.files.get(&icon))
.map(|icon| icon.clone()),
profile,
}
}
pub(crate) fn comment<'a>(&self, cache: &'a Cache) -> Option<&'a Comment> {
if let Item::Comment(id) = self.item {
cache.comments.get(&id)
} else {
None
}
}
pub(crate) async fn from_submission(
submission_id: Uuid,
viewer: Option<Uuid>,
mut cache: Cache,
state: &State,
) -> Result<(Self, Cache), Error> {
let store = state.profiles.clone();
let tup = actix_web::web::block(move || {
let node = Self::do_from_submission(submission_id, viewer, &mut cache, &store)?;
Ok((node, cache)) as Result<_, Error>
})
.await??;
Ok(tup)
}
fn do_from_submission(
submission_id: Uuid,
viewer: Option<Uuid>,
cache: &mut Cache,
state: &hyaenidae_profiles::State,
) -> Result<Self, Error> {
let submission = state.store.submissions.by_id(submission_id)?.req()?;
let author = state.store.profiles.by_id(submission.profile_id())?.req()?;
let author_id = author.id();
let is_self = viewer.map(|id| id == author.id()).unwrap_or(false);
let mut file_ids: Vec<_> = submission.files().iter().copied().collect();
file_ids.extend(author.icon());
file_ids.extend(author.banner());
for file_id in file_ids {
if !cache.files.contains_key(&file_id) {
let file = state.store.files.by_id(file_id)?.req()?;
cache.files.insert(file.id(), file);
}
}
cache.submissions.insert(submission.id(), submission);
cache.profiles.insert(author.id(), author);
let children = state
.store
.comments
.for_submission(submission_id)
.filter_map(|comment_id| state.store.comments.by_id(comment_id).ok()?)
.filter_map(|comment| {
if let Some(profile) = cache.profiles.get(&comment.profile_id()) {
CommentNode::do_from_root(comment, profile.clone(), viewer, cache, state)
} else {
let profile = state.store.profiles.by_id(comment.profile_id()).ok()??;
cache.profiles.insert(profile.id(), profile.clone());
CommentNode::do_from_root(comment, profile, viewer, cache, state)
}
})
.collect();
Ok(CommentNode {
item: Item::Submission(submission_id),
author_id,
is_self,
children,
})
}
pub(crate) async fn from_root(
comment: Comment,
author: Profile,
self_profile: Option<Uuid>,
mut cache: Cache,
state: &State,
) -> Result<(Self, Cache), Error> {
let store = state.profiles.clone();
let tup = actix_web::web::block(move || {
let node =
Self::do_from_root(comment, author, self_profile, &mut cache, &store).req()?;
Ok((node, cache)) as Result<_, Error>
})
.await??;
Ok(tup)
}
fn do_from_root(
comment: Comment,
author: Profile,
self_profile: Option<Uuid>,
cache: &mut Cache,
state: &hyaenidae_profiles::State,
) -> Option<Self> {
if let Some(self_id) = self_profile {
if !can_view_comment_no_recurse(self_id, &comment, state).unwrap_or(false) {
return None;
}
} else {
if !can_view_comment_logged_out_no_recurse(&comment, state).unwrap_or(false) {
return None;
}
}
let mut file_ids = vec![];
file_ids.extend(author.icon());
file_ids.extend(author.banner());
for file_id in file_ids {
if !cache.files.contains_key(&file_id) {
let file = state.store.files.by_id(file_id).ok()??;
cache.files.insert(file.id(), file);
}
}
let comment_id = comment.id();
let author_id = author.id();
let is_self = self_profile.map(|id| id == author.id()).unwrap_or(false);
cache.comments.insert(comment.id(), comment);
cache.profiles.insert(author.id(), author);
let children = state
.store
.comments
.replies_for(comment_id)
.filter_map(|comment_id| state.store.comments.by_id(comment_id).ok()?)
.filter_map(|comment| {
if let Some(profile) = cache.profiles.get(&comment.profile_id()) {
CommentNode::do_from_root(comment, profile.clone(), self_profile, cache, state)
} else {
let profile = state.store.profiles.by_id(comment.profile_id()).ok()??;
cache.profiles.insert(profile.id(), profile.clone());
CommentNode::do_from_root(comment, profile, self_profile, cache, state)
}
})
.rev()
.collect();
Some(CommentNode {
item: Item::Comment(comment_id),
author_id,
is_self,
children,
})
}
}