mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 05:31:10 +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>,
|
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
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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(()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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?;
|
Ok(DbEditCollection())
|
||||||
// TODO: return value propably not needed
|
|
||||||
Ok(DbEditCollection(edits))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue