1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-28 21:21:08 +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>,
data: Data<IbisData>,
) -> MyResult<Json<Vec<DbArticle>>> {
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

View file

@ -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<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 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<Vec<Self>> {

View file

@ -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<Self::DataType>,
) -> 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(
local_articles
.into_iter()
@ -66,12 +67,15 @@ impl Collection for DbArticleCollection {
_owner: &Self::Owner,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
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(()))
}

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> {
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,

View file

@ -22,7 +22,7 @@ pub struct ApubEditCollection {
}
#[derive(Clone, Debug)]
pub struct DbEditCollection(pub Vec<DbEdit>);
pub struct DbEditCollection();
#[async_trait::async_trait]
impl Collection for DbEditCollection {
@ -68,9 +68,7 @@ impl Collection for DbEditCollection {
_owner: &Self::Owner,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
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))
Ok(DbEditCollection())
}
}

View file

@ -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)
}
}

View file

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

View file

@ -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! {
<button
class="btn btn-sm"
on:click=move |_| follow_action.dispatch(instance.id)
prop:disabled=move || is_following
prop:hidden=move || instance.local
title="Follow the instance so that new edits are synchronized to your instance."
>
{follow_text}
</button>

View file

@ -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()

View file

@ -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::<RwSignal<GlobalState>>().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! {
<h1>{instance.domain}</h1>
<Show when=move || {
global_state.with(|state| state.my_profile.is_some())
}>
<div class="grid gap-3 mt-4">
<div class="flex flex-row items-center">
<h1 class="text-4xl font-bold font-serif w-full">
{instance.domain}
</h1>
<InstanceFollowButton instance=instance_.clone() />
</Show>
<p>
Follow the instance so that new edits are federated to your instance.
</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>
<div class="divider"></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
.list_articles(ListArticlesForm {
only_local: Some(false),
instance_id: None,
})
.await?;
assert_eq!(2, list_articles.len());