mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-25 17:21:23 +00:00
Some improvements to instance page
This commit is contained in:
parent
6cd87cf790
commit
5d47bb96ba
11 changed files with 105 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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(()))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?;
|
||||
Ok(DbEditCollection())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue