245 lines
7.5 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|