diff --git a/src/backend/api/article.rs b/src/backend/api/article.rs index 65ec48e..12f5bd4 100644 --- a/src/backend/api/article.rs +++ b/src/backend/api/article.rs @@ -176,8 +176,11 @@ pub(in crate::backend::api) async fn list_articles( Query(query): Query, data: Data, ) -> MyResult>> { - let only_local = query.only_local.unwrap_or(false); - Ok(Json(DbArticle::read_all(only_local, &data)?)) + Ok(Json(DbArticle::read_all( + query.only_local, + query.instance_id, + &data, + )?)) } /// Fork a remote article to local instance. This is useful if there are disagreements about diff --git a/src/backend/database/article.rs b/src/backend/database/article.rs index b382d99..dd2368d 100644 --- a/src/backend/database/article.rs +++ b/src/backend/database/article.rs @@ -135,20 +135,29 @@ impl DbArticle { } /// Read all articles, ordered by most recently edited first. - pub fn read_all(only_local: bool, data: &IbisData) -> MyResult> { + /// + /// TODO: Should get rid of only_local param and rely on instance_id + pub fn read_all( + only_local: Option, + instance_id: Option, + data: &IbisData, + ) -> MyResult> { let mut conn = data.db_pool.get()?; - let query = article::table + let mut query = article::table .inner_join(edit::table) + .inner_join(instance::table) .group_by(article::dsl::id) .order_by(max(edit::dsl::created).desc()) - .select(article::all_columns); - Ok(if only_local { - query - .filter(article::dsl::local.eq(true)) - .get_results(&mut conn)? - } else { - query.get_results(&mut conn)? - }) + .select(article::all_columns) + .into_boxed(); + + if let Some(true) = only_local { + query = query.filter(article::dsl::local.eq(true)); + } + 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> { diff --git a/src/backend/federation/objects/articles_collection.rs b/src/backend/federation/objects/articles_collection.rs index 9a1589d..bc65ec9 100644 --- a/src/backend/federation/objects/articles_collection.rs +++ b/src/backend/federation/objects/articles_collection.rs @@ -8,7 +8,8 @@ use activitypub_federation::{ protocol::verification::verify_domains_match, traits::{Collection, Object}, }; -use futures::{future, future::try_join_all}; +use futures::future::{self, join_all}; +use log::warn; use serde::{Deserialize, Serialize}; use url::Url; @@ -35,7 +36,7 @@ impl Collection for DbArticleCollection { owner: &Self::Owner, data: &Data, ) -> Result { - let local_articles = DbArticle::read_all(true, data)?; + let local_articles = DbArticle::read_all(Some(true), None, data)?; let articles = future::try_join_all( local_articles .into_iter() @@ -66,12 +67,15 @@ impl Collection for DbArticleCollection { _owner: &Self::Owner, data: &Data, ) -> Result { - try_join_all( - apub.items - .into_iter() - .map(|i| DbArticle::from_json(i, data)), - ) - .await?; + join_all(apub.items.into_iter().map(|article| async { + let id = article.id.clone(); + let res = DbArticle::from_json(article, data).await; + if let Err(e) = &res { + warn!("Failed to synchronize article {id}: {e}"); + } + res + })) + .await; Ok(DbArticleCollection(())) } diff --git a/src/backend/federation/objects/edit.rs b/src/backend/federation/objects/edit.rs index b320b09..1d37cfd 100644 --- a/src/backend/federation/objects/edit.rs +++ b/src/backend/federation/objects/edit.rs @@ -78,6 +78,7 @@ impl Object for DbEdit { async fn from_json(json: Self::Kind, data: &Data) -> Result { let article = json.object.dereference(data).await?; let creator = json.attributed_to.dereference(data).await?; + // TODO: if creator fails to fetch, make a dummy user let form = DbEditForm { creator_id: creator.id, ap_id: json.id, diff --git a/src/backend/federation/objects/edits_collection.rs b/src/backend/federation/objects/edits_collection.rs index 89173dd..3059958 100644 --- a/src/backend/federation/objects/edits_collection.rs +++ b/src/backend/federation/objects/edits_collection.rs @@ -22,7 +22,7 @@ pub struct ApubEditCollection { } #[derive(Clone, Debug)] -pub struct DbEditCollection(pub Vec); +pub struct DbEditCollection(); #[async_trait::async_trait] impl Collection for DbEditCollection { @@ -68,9 +68,7 @@ impl Collection for DbEditCollection { _owner: &Self::Owner, data: &Data, ) -> Result { - let edits = - try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?; - // TODO: return value propably not needed - Ok(DbEditCollection(edits)) + try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?; + Ok(DbEditCollection()) } } diff --git a/src/backend/federation/objects/instance.rs b/src/backend/federation/objects/instance.rs index df8f4e7..8c25f1f 100644 --- a/src/backend/federation/objects/instance.rs +++ b/src/backend/federation/objects/instance.rs @@ -114,8 +114,17 @@ impl Object for DbInstance { local: false, }; let instance = DbInstance::create(&form, data)?; + // 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) } } diff --git a/src/common/mod.rs b/src/common/mod.rs index fe48890..da8b58e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -29,6 +29,7 @@ pub struct GetArticleForm { #[derive(Deserialize, Serialize, Clone)] pub struct ListArticlesForm { pub only_local: Option, + pub instance_id: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] diff --git a/src/frontend/components/instance_follow_button.rs b/src/frontend/components/instance_follow_button.rs index 6e60362..ea2b00c 100644 --- a/src/frontend/components/instance_follow_button.rs +++ b/src/frontend/components/instance_follow_button.rs @@ -22,7 +22,7 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView { .get_untracked() .my_profile .map(|p| p.following.contains(&instance)) - .unwrap_or_default(); + .unwrap_or(false); let follow_text = if is_following { "Following instance" } else { @@ -31,9 +31,11 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView { view! { diff --git a/src/frontend/pages/article/list.rs b/src/frontend/pages/article/list.rs index fa9eaf0..6bf2aea 100644 --- a/src/frontend/pages/article/list.rs +++ b/src/frontend/pages/article/list.rs @@ -16,6 +16,7 @@ pub fn ListArticles() -> impl IntoView { GlobalState::api_client() .list_articles(ListArticlesForm { only_local: Some(only_local), + instance_id: None, }) .await .unwrap() diff --git a/src/frontend/pages/instance_details.rs b/src/frontend/pages/instance_details.rs index 57d1eba..1cfe582 100644 --- a/src/frontend/pages/instance_details.rs +++ b/src/frontend/pages/instance_details.rs @@ -1,6 +1,11 @@ use crate::{ - common::{utils::http_protocol_str, DbInstance}, - frontend::{app::GlobalState, components::instance_follow_button::InstanceFollowButton}, + common::{utils::http_protocol_str, DbInstance, ListArticlesForm}, + frontend::{ + app::GlobalState, + article_link, + article_title, + components::instance_follow_button::InstanceFollowButton, + }, }; use leptos::*; use leptos_router::use_params_map; @@ -8,7 +13,6 @@ use url::Url; #[component] pub fn InstanceDetails() -> impl IntoView { - let global_state = use_context::>().unwrap(); let params = use_params_map(); let hostname = move || params.get().get("hostname").cloned().unwrap(); let instance_profile = create_resource(hostname, move |hostname| async move { @@ -27,25 +31,52 @@ pub fn InstanceDetails() -> impl IntoView { instance_profile .get() .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(); view! { -

{instance.domain}

- - +
+
+

+ {instance.domain} +

- -

- Follow the instance so that new edits are federated to your instance. -

-

- "TODO: show a list of articles from the instance. For now you can use the " - Article list. -

-
-

"Description:"

+
+ +
{instance.description}
+

Articles

+
    + {move || { + articles + .get() + .map(|a| { + a.into_iter() + .map(|a| { + view! { +
  • + + {article_title(&a)} + +
  • + } + }) + .collect::>() + }) + }} + +
+
} }) }} diff --git a/tests/test.rs b/tests/test.rs index 712bdad..0a0e08d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -76,6 +76,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> { .alpha .list_articles(ListArticlesForm { only_local: Some(false), + instance_id: None, }) .await?; assert_eq!(2, list_articles.len());