1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-25 07:41:09 +00:00

Some improvements to instance page

This commit is contained in:
Felix Ableitner 2024-11-08 12:26:06 +01:00
parent 6cd87cf790
commit 5d47bb96ba
11 changed files with 105 additions and 45 deletions

View file

@ -176,8 +176,11 @@ pub(in crate::backend::api) async fn list_articles(
Query(query): Query<ListArticlesForm>, Query(query): Query<ListArticlesForm>,
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<Json<Vec<DbArticle>>> { ) -> MyResult<Json<Vec<DbArticle>>> {
let only_local = query.only_local.unwrap_or(false); Ok(Json(DbArticle::read_all(
Ok(Json(DbArticle::read_all(only_local, &data)?)) query.only_local,
query.instance_id,
&data,
)?))
} }
/// Fork a remote article to local instance. This is useful if there are disagreements about /// Fork a remote article to local instance. This is useful if there are disagreements about

View file

@ -135,20 +135,29 @@ impl DbArticle {
} }
/// Read all articles, ordered by most recently edited first. /// Read all articles, ordered by most recently edited first.
pub fn read_all(only_local: bool, data: &IbisData) -> MyResult<Vec<Self>> { ///
/// TODO: Should get rid of only_local param and rely on instance_id
pub fn read_all(
only_local: Option<bool>,
instance_id: Option<i32>,
data: &IbisData,
) -> MyResult<Vec<Self>> {
let mut conn = data.db_pool.get()?; let mut conn = data.db_pool.get()?;
let query = article::table let mut query = article::table
.inner_join(edit::table) .inner_join(edit::table)
.inner_join(instance::table)
.group_by(article::dsl::id) .group_by(article::dsl::id)
.order_by(max(edit::dsl::created).desc()) .order_by(max(edit::dsl::created).desc())
.select(article::all_columns); .select(article::all_columns)
Ok(if only_local { .into_boxed();
query
.filter(article::dsl::local.eq(true)) if let Some(true) = only_local {
.get_results(&mut conn)? query = query.filter(article::dsl::local.eq(true));
} else { }
query.get_results(&mut conn)? if let Some(instance_id) = instance_id {
}) query = query.filter(instance::dsl::id.eq(instance_id));
}
Ok(query.get_results(&mut conn)?)
} }
pub fn search(query: &str, data: &IbisData) -> MyResult<Vec<Self>> { pub fn search(query: &str, data: &IbisData) -> MyResult<Vec<Self>> {

View file

@ -8,7 +8,8 @@ use activitypub_federation::{
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{Collection, Object}, traits::{Collection, Object},
}; };
use futures::{future, future::try_join_all}; use futures::future::{self, join_all};
use log::warn;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
@ -35,7 +36,7 @@ impl Collection for DbArticleCollection {
owner: &Self::Owner, owner: &Self::Owner,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Self::Kind, Self::Error> { ) -> Result<Self::Kind, Self::Error> {
let local_articles = DbArticle::read_all(true, data)?; let local_articles = DbArticle::read_all(Some(true), None, data)?;
let articles = future::try_join_all( let articles = future::try_join_all(
local_articles local_articles
.into_iter() .into_iter()
@ -66,12 +67,15 @@ impl Collection for DbArticleCollection {
_owner: &Self::Owner, _owner: &Self::Owner,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
try_join_all( join_all(apub.items.into_iter().map(|article| async {
apub.items let id = article.id.clone();
.into_iter() let res = DbArticle::from_json(article, data).await;
.map(|i| DbArticle::from_json(i, data)), if let Err(e) = &res {
) warn!("Failed to synchronize article {id}: {e}");
.await?; }
res
}))
.await;
Ok(DbArticleCollection(())) Ok(DbArticleCollection(()))
} }

View file

@ -78,6 +78,7 @@ impl Object for DbEdit {
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> { async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
let article = json.object.dereference(data).await?; let article = json.object.dereference(data).await?;
let creator = json.attributed_to.dereference(data).await?; let creator = json.attributed_to.dereference(data).await?;
// TODO: if creator fails to fetch, make a dummy user
let form = DbEditForm { let form = DbEditForm {
creator_id: creator.id, creator_id: creator.id,
ap_id: json.id, ap_id: json.id,

View file

@ -22,7 +22,7 @@ pub struct ApubEditCollection {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DbEditCollection(pub Vec<DbEdit>); pub struct DbEditCollection();
#[async_trait::async_trait] #[async_trait::async_trait]
impl Collection for DbEditCollection { impl Collection for DbEditCollection {
@ -68,9 +68,7 @@ impl Collection for DbEditCollection {
_owner: &Self::Owner, _owner: &Self::Owner,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let edits =
try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?; try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?;
// TODO: return value propably not needed Ok(DbEditCollection())
Ok(DbEditCollection(edits))
} }
} }

View file

@ -114,8 +114,17 @@ impl Object for DbInstance {
local: false, local: false,
}; };
let instance = DbInstance::create(&form, data)?; let instance = DbInstance::create(&form, data)?;
// TODO: very inefficient to sync all articles every time // TODO: very inefficient to sync all articles every time
instance.articles_url.dereference(&instance, data).await?; let instance_ = instance.clone();
let data_ = data.reset_request_count();
tokio::spawn(async move {
let res = instance_.articles_url.dereference(&instance_, &data_).await;
if let Err(e) = res {
tracing::warn!("error in spawn: {e}");
}
});
Ok(instance) Ok(instance)
} }
} }

View file

@ -29,6 +29,7 @@ pub struct GetArticleForm {
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct ListArticlesForm { pub struct ListArticlesForm {
pub only_local: Option<bool>, pub only_local: Option<bool>,
pub instance_id: Option<i32>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]

View file

@ -22,7 +22,7 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
.get_untracked() .get_untracked()
.my_profile .my_profile
.map(|p| p.following.contains(&instance)) .map(|p| p.following.contains(&instance))
.unwrap_or_default(); .unwrap_or(false);
let follow_text = if is_following { let follow_text = if is_following {
"Following instance" "Following instance"
} else { } else {
@ -31,9 +31,11 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
view! { view! {
<button <button
class="btn btn-sm"
on:click=move |_| follow_action.dispatch(instance.id) on:click=move |_| follow_action.dispatch(instance.id)
prop:disabled=move || is_following prop:disabled=move || is_following
prop:hidden=move || instance.local prop:hidden=move || instance.local
title="Follow the instance so that new edits are synchronized to your instance."
> >
{follow_text} {follow_text}
</button> </button>

View file

@ -16,6 +16,7 @@ pub fn ListArticles() -> impl IntoView {
GlobalState::api_client() GlobalState::api_client()
.list_articles(ListArticlesForm { .list_articles(ListArticlesForm {
only_local: Some(only_local), only_local: Some(only_local),
instance_id: None,
}) })
.await .await
.unwrap() .unwrap()

View file

@ -1,6 +1,11 @@
use crate::{ use crate::{
common::{utils::http_protocol_str, DbInstance}, common::{utils::http_protocol_str, DbInstance, ListArticlesForm},
frontend::{app::GlobalState, components::instance_follow_button::InstanceFollowButton}, frontend::{
app::GlobalState,
article_link,
article_title,
components::instance_follow_button::InstanceFollowButton,
},
}; };
use leptos::*; use leptos::*;
use leptos_router::use_params_map; use leptos_router::use_params_map;
@ -8,7 +13,6 @@ use url::Url;
#[component] #[component]
pub fn InstanceDetails() -> impl IntoView { pub fn InstanceDetails() -> impl IntoView {
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
let params = use_params_map(); let params = use_params_map();
let hostname = move || params.get().get("hostname").cloned().unwrap(); let hostname = move || params.get().get("hostname").cloned().unwrap();
let instance_profile = create_resource(hostname, move |hostname| async move { let instance_profile = create_resource(hostname, move |hostname| async move {
@ -27,25 +31,52 @@ pub fn InstanceDetails() -> impl IntoView {
instance_profile instance_profile
.get() .get()
.map(|instance: DbInstance| { .map(|instance: DbInstance| {
let articles = create_resource(
move || instance.id,
|instance_id| async move {
GlobalState::api_client()
.list_articles(ListArticlesForm {
only_local: None,
instance_id: Some(instance_id),
})
.await
.unwrap()
},
);
let instance_ = instance.clone(); let instance_ = instance.clone();
view! { view! {
<h1>{instance.domain}</h1> <div class="grid gap-3 mt-4">
<div class="flex flex-row items-center">
<Show when=move || { <h1 class="text-4xl font-bold font-serif w-full">
global_state.with(|state| state.my_profile.is_some()) {instance.domain}
}> </h1>
<InstanceFollowButton instance=instance_.clone() /> <InstanceFollowButton instance=instance_.clone() />
</Show> </div>
<p>
Follow the instance so that new edits are federated to your instance. <div class="divider"></div>
</p>
<p>
"TODO: show a list of articles from the instance. For now you can use the "
<a href="/article/list">Article list</a>.
</p>
<hr />
<h2>"Description:"</h2>
<div>{instance.description}</div> <div>{instance.description}</div>
<h2 class="text-xl font-bold font-serif">Articles</h2>
<ul class="list-none">
{move || {
articles
.get()
.map(|a| {
a.into_iter()
.map(|a| {
view! {
<li>
<a class="link text-lg" href=article_link(&a)>
{article_title(&a)}
</a>
</li>
}
})
.collect::<Vec<_>>()
})
}}
</ul>
</div>
} }
}) })
}} }}

View file

@ -76,6 +76,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
.alpha .alpha
.list_articles(ListArticlesForm { .list_articles(ListArticlesForm {
only_local: Some(false), only_local: Some(false),
instance_id: None,
}) })
.await?; .await?;
assert_eq!(2, list_articles.len()); assert_eq!(2, list_articles.len());