251 lines
7 KiB
Rust
251 lines
7 KiB
Rust
use crate::{
|
|
error::{Error, OptionExt},
|
|
middleware::UserProfile,
|
|
nav::NavState,
|
|
pagination::{Page, PageSource, Pagination},
|
|
views::SubmissionView,
|
|
ActixLoader, State,
|
|
};
|
|
use actix_web::{web, HttpResponse, Scope};
|
|
use hyaenidae_profiles::store::{File, Profile, Submission};
|
|
use hyaenidae_toolkit::Button;
|
|
use i18n_embed_fl::fl;
|
|
use std::collections::HashMap;
|
|
use uuid::Uuid;
|
|
|
|
pub(super) fn scope() -> Scope {
|
|
web::scope("/feed").route("", web::get().to(feed_page))
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize)]
|
|
#[serde(untagged)]
|
|
enum PageQuery {
|
|
Min { min: Uuid },
|
|
Max { max: Uuid },
|
|
}
|
|
|
|
impl From<PageQuery> for PageSource {
|
|
fn from(p: PageQuery) -> Self {
|
|
match p {
|
|
PageQuery::Max { max } => PageSource::OlderThan(max),
|
|
PageQuery::Min { min } => PageSource::NewerThan(min),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn feed_page(
|
|
loader: ActixLoader,
|
|
profile: UserProfile,
|
|
page: Option<web::Query<PageQuery>>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let view =
|
|
ViewFeedState::build(profile.0.id(), page.map(|p| p.into_inner().into()), &state).await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::feed::index(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct ViewFeedCache {
|
|
submissions: HashMap<Uuid, Submission>,
|
|
files: HashMap<Uuid, File>,
|
|
profiles: HashMap<Uuid, Profile>,
|
|
}
|
|
|
|
impl ViewFeedCache {
|
|
fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
}
|
|
|
|
pub struct ViewFeedState {
|
|
cache: ViewFeedCache,
|
|
pub(crate) profile: Profile,
|
|
submission_page: Page,
|
|
}
|
|
|
|
impl ViewFeedState {
|
|
pub(crate) fn submissions<'a>(&'a self) -> impl Iterator<Item = SubmissionView<'a>> + 'a {
|
|
self.submission_page
|
|
.items
|
|
.iter()
|
|
.filter_map(move |submission_id| {
|
|
let submission = self.cache.submissions.get(submission_id)?;
|
|
let author = self.cache.profiles.get(&submission.profile_id())?;
|
|
let files = submission
|
|
.files()
|
|
.iter()
|
|
.filter_map(move |file_id| self.cache.files.get(file_id))
|
|
.collect();
|
|
|
|
Some(SubmissionView {
|
|
author,
|
|
submission,
|
|
files,
|
|
})
|
|
})
|
|
}
|
|
|
|
pub(crate) fn has_submissions(&self) -> bool {
|
|
!self.submission_page.items.is_empty()
|
|
}
|
|
|
|
pub(crate) fn has_nav(&self) -> bool {
|
|
self.submission_page.next.is_some() || self.submission_page.prev.is_some()
|
|
}
|
|
|
|
pub(crate) fn nav(&self, loader: &ActixLoader) -> Vec<Button> {
|
|
let mut buttons = vec![];
|
|
|
|
if let Some(id) = self.submission_page.prev {
|
|
buttons
|
|
.push(Button::secondary(&fl!(loader, "previous-button")).href(&self.min_path(id)));
|
|
}
|
|
|
|
if let Some(id) = self.submission_page.next {
|
|
buttons.push(Button::secondary(&fl!(loader, "next-button")).href(&self.max_path(id)));
|
|
}
|
|
|
|
buttons
|
|
}
|
|
|
|
fn min_path(&self, id: Uuid) -> String {
|
|
format!("/feed?min={}", id)
|
|
}
|
|
|
|
fn max_path(&self, id: Uuid) -> String {
|
|
format!("/feed?max={}", id)
|
|
}
|
|
|
|
async fn build(
|
|
profile_id: Uuid,
|
|
source: Option<PageSource>,
|
|
state: &State,
|
|
) -> Result<Self, Error> {
|
|
let store = state.profiles.clone();
|
|
|
|
let state = web::block(move || {
|
|
let mut cache = ViewFeedCache::new();
|
|
|
|
let profile = store.store.profiles.by_id(profile_id)?.req()?;
|
|
|
|
if let Some(file_id) = profile.icon() {
|
|
if !cache.files.contains_key(&file_id) {
|
|
let file = store.store.files.by_id(file_id)?.req()?;
|
|
cache.files.insert(file.id(), file);
|
|
}
|
|
}
|
|
|
|
if let Some(file_id) = profile.banner() {
|
|
if !cache.files.contains_key(&file_id) {
|
|
let file = store.store.files.by_id(file_id)?.req()?;
|
|
cache.files.insert(file.id(), file);
|
|
}
|
|
}
|
|
|
|
let submission_page = Page::from_pagination(
|
|
FeedPager {
|
|
profile_id,
|
|
store: &store,
|
|
cache: &mut cache,
|
|
},
|
|
24,
|
|
source,
|
|
);
|
|
|
|
let state = ViewFeedState {
|
|
cache,
|
|
profile,
|
|
submission_page,
|
|
};
|
|
|
|
Ok(state) as Result<Self, Error>
|
|
})
|
|
.await??;
|
|
|
|
Ok(state)
|
|
}
|
|
}
|
|
|
|
struct FeedPager<'b> {
|
|
profile_id: Uuid,
|
|
store: &'b hyaenidae_profiles::State,
|
|
cache: &'b mut ViewFeedCache,
|
|
}
|
|
|
|
impl<'b> Pagination for FeedPager<'b> {
|
|
fn from_max<'a>(&'a mut self, max: Uuid) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
|
|
Box::new(
|
|
self.store
|
|
.store
|
|
.view
|
|
.submissions
|
|
.older_than_for_profile(self.profile_id, max)
|
|
.filter_map(move |submission_id| self.cache_submission(submission_id)),
|
|
)
|
|
}
|
|
|
|
fn from_min<'a>(&'a mut self, min: Uuid) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
|
|
Box::new(
|
|
self.store
|
|
.store
|
|
.view
|
|
.submissions
|
|
.newer_than_for_profile(self.profile_id, min)
|
|
.filter_map(move |submission_id| self.cache_submission(submission_id)),
|
|
)
|
|
}
|
|
|
|
fn from_start<'a>(&'a mut self) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
|
|
Box::new(
|
|
self.store
|
|
.store
|
|
.view
|
|
.submissions
|
|
.for_profile(self.profile_id)
|
|
.filter_map(move |submission_id| self.cache_submission(submission_id)),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> FeedPager<'a> {
|
|
fn cache_submission(&mut self, submission_id: Uuid) -> Option<Uuid> {
|
|
if !self.cache.submissions.contains_key(&submission_id) {
|
|
let submission = self.store.store.submissions.by_id(submission_id).ok()??;
|
|
|
|
let mut file_ids = vec![];
|
|
|
|
if !self.cache.profiles.contains_key(&submission.profile_id()) {
|
|
let profile = self
|
|
.store
|
|
.store
|
|
.profiles
|
|
.by_id(submission.profile_id())
|
|
.ok()??;
|
|
|
|
file_ids.extend(profile.icon());
|
|
file_ids.extend(profile.banner());
|
|
|
|
self.cache.profiles.insert(profile.id(), profile);
|
|
}
|
|
|
|
file_ids.extend(submission.files());
|
|
|
|
for file_id in file_ids {
|
|
if !self.cache.files.contains_key(&file_id) {
|
|
let file = self.store.store.files.by_id(file_id).ok()??;
|
|
|
|
self.cache.files.insert(file.id(), file);
|
|
}
|
|
}
|
|
|
|
self.cache.submissions.insert(submission.id(), submission);
|
|
}
|
|
|
|
Some(submission_id)
|
|
}
|
|
}
|