mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 16:41:09 +00:00
correctly link remote articles
This commit is contained in:
parent
773e19b38b
commit
45d5fc9a2e
14 changed files with 89 additions and 40 deletions
|
@ -10,8 +10,8 @@ set -e
|
||||||
|
|
||||||
function cleanup {
|
function cleanup {
|
||||||
echo "stop postgres"
|
echo "stop postgres"
|
||||||
./scripts/stop_dev_db.sh $ALPHA_DB_PATH
|
./scripts/stop_dev_db.sh $ALPHA_DB_PATH >/dev/null
|
||||||
./scripts/stop_dev_db.sh $BETA_DB_PATH
|
./scripts/stop_dev_db.sh $BETA_DB_PATH >/dev/null
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
@ -22,14 +22,14 @@ BETA_DB_PATH="$DB_FOLDER/beta"
|
||||||
|
|
||||||
# create db folders if they dont exist
|
# create db folders if they dont exist
|
||||||
if [ ! -d $ALPHA_DB_PATH ]; then
|
if [ ! -d $ALPHA_DB_PATH ]; then
|
||||||
./scripts/start_dev_db.sh $ALPHA_DB_PATH
|
./scripts/start_dev_db.sh $ALPHA_DB_PATH >/dev/null
|
||||||
else
|
else
|
||||||
pg_ctl start --options="-c listen_addresses= -c unix_socket_directories=$ALPHA_DB_PATH" -D "$ALPHA_DB_PATH/dev_pgdata"
|
pg_ctl start --options="-c listen_addresses= -c unix_socket_directories=$ALPHA_DB_PATH" -D "$ALPHA_DB_PATH/dev_pgdata" >/dev/null
|
||||||
fi
|
fi
|
||||||
if [ ! -d $BETA_DB_PATH ]; then
|
if [ ! -d $BETA_DB_PATH ]; then
|
||||||
./scripts/start_dev_db.sh $BETA_DB_PATH
|
./scripts/start_dev_db.sh $BETA_DB_PATH >/dev/null
|
||||||
else
|
else
|
||||||
pg_ctl start --options="-c listen_addresses= -c unix_socket_directories=$BETA_DB_PATH" -D "$BETA_DB_PATH/dev_pgdata"
|
pg_ctl start --options="-c listen_addresses= -c unix_socket_directories=$BETA_DB_PATH" -D "$BETA_DB_PATH/dev_pgdata" >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ALPHA_DB_URL="postgresql://ibis:password@/ibis?host=$ALPHA_DB_PATH"
|
ALPHA_DB_URL="postgresql://ibis:password@/ibis?host=$ALPHA_DB_PATH"
|
||||||
|
|
|
@ -140,12 +140,12 @@ pub(in crate::backend::api) async fn get_article(
|
||||||
match (query.title, query.id) {
|
match (query.title, query.id) {
|
||||||
(Some(title), None) => Ok(Json(DbArticle::read_view_title(
|
(Some(title), None) => Ok(Json(DbArticle::read_view_title(
|
||||||
&title,
|
&title,
|
||||||
&query.instance_id,
|
&query.instance_domain,
|
||||||
&data.db_connection,
|
&data.db_connection,
|
||||||
)?)),
|
)?)),
|
||||||
(None, Some(id)) => {
|
(None, Some(id)) => {
|
||||||
if query.instance_id.is_some() {
|
if query.instance_domain.is_some() {
|
||||||
return Err(anyhow!("Cant combine id and instance_id").into());
|
return Err(anyhow!("Cant combine id and instance_domain").into());
|
||||||
}
|
}
|
||||||
Ok(Json(DbArticle::read_view(id, &data.db_connection)?))
|
Ok(Json(DbArticle::read_view(id, &data.db_connection)?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::backend::database::schema::{article, edit};
|
use crate::backend::database::schema::{article, edit, instance};
|
||||||
use crate::backend::error::MyResult;
|
use crate::backend::error::MyResult;
|
||||||
use crate::backend::federation::objects::edits_collection::DbEditCollection;
|
use crate::backend::federation::objects::edits_collection::DbEditCollection;
|
||||||
use crate::common::DbEdit;
|
use crate::common::DbEdit;
|
||||||
|
@ -77,16 +77,19 @@ impl DbArticle {
|
||||||
|
|
||||||
pub fn read_view_title(
|
pub fn read_view_title(
|
||||||
title: &str,
|
title: &str,
|
||||||
instance_id: &Option<i32>,
|
instance_domain: &Option<String>,
|
||||||
conn: &Mutex<PgConnection>,
|
conn: &Mutex<PgConnection>,
|
||||||
) -> MyResult<ArticleView> {
|
) -> MyResult<ArticleView> {
|
||||||
let article: DbArticle = {
|
let article: DbArticle = {
|
||||||
let mut conn = conn.lock().unwrap();
|
let mut conn = conn.lock().unwrap();
|
||||||
let query = article::table
|
let query = article::table
|
||||||
.into_boxed()
|
.into_boxed()
|
||||||
.filter(article::dsl::title.eq(title));
|
.filter(article::dsl::title.eq(title))
|
||||||
let query = if let Some(instance_id) = instance_id {
|
.left_join(instance::table);
|
||||||
query.filter(article::dsl::instance_id.eq(instance_id))
|
let query = if let Some(instance_domain) = instance_domain {
|
||||||
|
// TODO: fragile
|
||||||
|
let ap_id = format!("http://{instance_domain}/");
|
||||||
|
query.filter(instance::dsl::ap_id.eq(ap_id))
|
||||||
} else {
|
} else {
|
||||||
query.filter(article::dsl::local.eq(true))
|
query.filter(article::dsl::local.eq(true))
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub const MAIN_PAGE_NAME: &str = "Main_Page";
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct GetArticleData {
|
pub struct GetArticleData {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub instance_id: Option<i32>,
|
pub instance_domain: Option<String>,
|
||||||
pub id: Option<i32>,
|
pub id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +54,6 @@ pub struct DbArticle {
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbArticle {
|
|
||||||
pub fn title(&self) -> String {
|
|
||||||
self.title.replace('_', " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single change to the article.
|
/// Represents a single change to the article.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
|
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl ApiClient {
|
||||||
|
|
||||||
self.get_article(GetArticleData {
|
self.get_article(GetArticleData {
|
||||||
title: None,
|
title: None,
|
||||||
instance_id: None,
|
instance_domain: None,
|
||||||
id: Some(edit_form.article_id),
|
id: Some(edit_form.article_id),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::validation::can_edit_article;
|
use crate::common::validation::can_edit_article;
|
||||||
use crate::common::ArticleView;
|
use crate::common::ArticleView;
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
|
use crate::frontend::article_link;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
|
@ -10,16 +11,16 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
||||||
view! {
|
view! {
|
||||||
<Suspense>
|
<Suspense>
|
||||||
{move || article.get().map(|article| {
|
{move || article.get().map(|article| {
|
||||||
let title = article.article.title.clone();
|
let article_link = article_link(&article.article);
|
||||||
view!{
|
view!{
|
||||||
<nav class="inner">
|
<nav class="inner">
|
||||||
<A href={format!("/article/{title}")}>"Read"</A>
|
<A href=article_link.clone()>"Read"</A>
|
||||||
<A href={format!("/article/{title}/history")}>"History"</A>
|
<A href={format!("{article_link}/history")}>"History"</A>
|
||||||
<Show when=move || global_state.with(|state| {
|
<Show when=move || global_state.with(|state| {
|
||||||
let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false);
|
let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false);
|
||||||
state.my_profile.is_some() && can_edit_article(&article.article, is_admin).is_ok()
|
state.my_profile.is_some() && can_edit_article(&article.article, is_admin).is_ok()
|
||||||
})>
|
})>
|
||||||
<A href={format!("/article/{title}/edit")}>"Edit"</A>
|
<A href={format!("{article_link}/edit")}>"Edit"</A>
|
||||||
</Show>
|
</Show>
|
||||||
</nav>
|
</nav>
|
||||||
}})}
|
}})}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use crate::common::DbArticle;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod app;
|
pub mod app;
|
||||||
mod components;
|
mod components;
|
||||||
|
@ -7,3 +10,37 @@ pub mod pages;
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
pub fn hydrate() {}
|
pub fn hydrate() {}
|
||||||
|
|
||||||
|
fn extract_hostname(article: &DbArticle) -> String {
|
||||||
|
let ap_id: Url;
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
{
|
||||||
|
ap_id = article.ap_id.parse().unwrap();
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
{
|
||||||
|
ap_id = article.ap_id.inner().clone();
|
||||||
|
}
|
||||||
|
let mut port = String::new();
|
||||||
|
if let Some(port_) = ap_id.port() {
|
||||||
|
port = format!(":{port_}");
|
||||||
|
}
|
||||||
|
format!("{}{port}", ap_id.host_str().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn article_link(article: &DbArticle) -> String {
|
||||||
|
if article.local {
|
||||||
|
format!("/article/{}", article.title)
|
||||||
|
} else {
|
||||||
|
format!("/article/{}@{}", article.title, extract_hostname(article))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn article_title(article: &DbArticle) -> String {
|
||||||
|
let title = article.title.replace('_', " ");
|
||||||
|
if article.local {
|
||||||
|
title
|
||||||
|
} else {
|
||||||
|
format!("{}@{}", title, extract_hostname(article))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::common::{ArticleView, EditArticleData};
|
use crate::common::{ArticleView, EditArticleData};
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
|
use crate::frontend::article_title;
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
use crate::frontend::components::article_nav::ArticleNav;
|
||||||
use crate::frontend::pages::article_resource;
|
use crate::frontend::pages::article_resource;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
@ -61,7 +62,7 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
set_text.set(article.article.text.clone());
|
set_text.set(article.article.text.clone());
|
||||||
view! {
|
view! {
|
||||||
<div class="item-view">
|
<div class="item-view">
|
||||||
<h1>{article.article.title()}</h1>
|
<h1>{article_title(&article.article)}</h1>
|
||||||
<textarea on:keyup=move |ev| {
|
<textarea on:keyup=move |ev| {
|
||||||
let val = event_target_value(&ev);
|
let val = event_target_value(&ev);
|
||||||
set_text.update(|p| *p = val);
|
set_text.update(|p| *p = val);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::frontend::article_title;
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
use crate::frontend::components::article_nav::ArticleNav;
|
||||||
use crate::frontend::pages::article_resource;
|
use crate::frontend::pages::article_resource;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
@ -15,7 +16,7 @@ pub fn ArticleHistory() -> impl IntoView {
|
||||||
move || article.get().map(|article| {
|
move || article.get().map(|article| {
|
||||||
view! {
|
view! {
|
||||||
<div class="item-view">
|
<div class="item-view">
|
||||||
<h1>{article.article.title()}</h1>
|
<h1>{article_title(&article.article)}</h1>
|
||||||
{
|
{
|
||||||
article.edits.into_iter().rev().map(|edit| {
|
article.edits.into_iter().rev().map(|edit| {
|
||||||
let path = format!("/article/{}/diff/{}", article.article.title, edit.hash.0);
|
let path = format!("/article/{}/diff/{}", article.article.title, edit.hash.0);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::common::ListArticlesData;
|
use crate::common::ListArticlesData;
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
|
use crate::frontend::{article_link, article_title};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use web_sys::wasm_bindgen::JsCast;
|
use web_sys::wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ pub fn ListArticles() -> impl IntoView {
|
||||||
<ul> {
|
<ul> {
|
||||||
move || articles.get().map(|a|
|
move || articles.get().map(|a|
|
||||||
a.into_iter().map(|a| view! {
|
a.into_iter().map(|a| view! {
|
||||||
<li><a href=format!("/article/{}", a.title)>{a.title()}</a></li>
|
<li><a href=article_link(&a)>{article_title(&a)}</a></li>
|
||||||
}).collect::<Vec<_>>())
|
}).collect::<Vec<_>>())
|
||||||
} </ul>
|
} </ul>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::frontend::article_title;
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
use crate::frontend::components::article_nav::ArticleNav;
|
||||||
use crate::frontend::pages::article_resource;
|
use crate::frontend::pages::article_resource;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
@ -17,7 +18,7 @@ pub fn ReadArticle() -> impl IntoView {
|
||||||
move || article.get().map(|article|
|
move || article.get().map(|article|
|
||||||
view! {
|
view! {
|
||||||
<div class="item-view">
|
<div class="item-view">
|
||||||
<h1>{article.article.title()}</h1>
|
<h1>{article_title(&article.article)}</h1>
|
||||||
<div inner_html={parser.parse(&article.article.text).render()}/>
|
<div inner_html={parser.parse(&article.article.text).render()}/>
|
||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,11 +13,16 @@ fn article_resource(
|
||||||
title: impl Fn() -> Option<String> + 'static,
|
title: impl Fn() -> Option<String> + 'static,
|
||||||
) -> Resource<Option<String>, ArticleView> {
|
) -> Resource<Option<String>, ArticleView> {
|
||||||
create_resource(title, move |title| async move {
|
create_resource(title, move |title| async move {
|
||||||
let title = title.unwrap_or(MAIN_PAGE_NAME.to_string());
|
let mut title = title.unwrap_or(MAIN_PAGE_NAME.to_string());
|
||||||
|
let mut domain = None;
|
||||||
|
if let Some((title_, domain_)) = title.clone().split_once('@') {
|
||||||
|
title = title_.to_string();
|
||||||
|
domain = Some(domain_.to_string());
|
||||||
|
}
|
||||||
GlobalState::api_client()
|
GlobalState::api_client()
|
||||||
.get_article(GetArticleData {
|
.get_article(GetArticleData {
|
||||||
title: Some(title),
|
title: Some(title),
|
||||||
instance_id: None,
|
instance_domain: domain,
|
||||||
id: None,
|
id: None,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::common::{DbArticle, DbInstance, SearchArticleData};
|
use crate::common::{DbArticle, DbInstance, SearchArticleData};
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
|
use crate::frontend::{article_link, article_title};
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_query_map;
|
use leptos_router::use_query_map;
|
||||||
|
@ -63,7 +64,7 @@ pub fn Search() -> impl IntoView {
|
||||||
search_results.articles
|
search_results.articles
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| view! { <li>
|
.map(|a| view! { <li>
|
||||||
<a href={format!("/article/{}", a.title)}>{a.title()}</a>
|
<a href={article_link(a)}>{article_title(a)}</a>
|
||||||
</li>})
|
</li>})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
// now article can be read
|
// now article can be read
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleData {
|
||||||
title: Some(create_res.article.title.clone()),
|
title: Some(create_res.article.title.clone()),
|
||||||
instance_id: None,
|
instance_domain: None,
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
let get_res = data.alpha.get_article(get_article_data.clone()).await?;
|
let get_res = data.alpha.get_article(get_article_data.clone()).await?;
|
||||||
|
@ -150,7 +150,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
|
|
||||||
let mut get_article_data = GetArticleData {
|
let mut get_article_data = GetArticleData {
|
||||||
title: Some(create_res.article.title),
|
title: Some(create_res.article.title),
|
||||||
instance_id: None,
|
instance_domain: None,
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
assert!(get_res.is_err());
|
assert!(get_res.is_err());
|
||||||
|
|
||||||
// get the article with instance id and compare
|
// get the article with instance id and compare
|
||||||
get_article_data.instance_id = Some(instance.id);
|
get_article_data.instance_domain = Some(instance.ap_id.to_string());
|
||||||
let get_res = data.beta.get_article(get_article_data).await?;
|
let get_res = data.beta.get_article(get_article_data).await?;
|
||||||
assert_eq!(create_res.article.ap_id, get_res.article.ap_id);
|
assert_eq!(create_res.article.ap_id, get_res.article.ap_id);
|
||||||
assert_eq!(create_form.title, get_res.article.title);
|
assert_eq!(create_form.title, get_res.article.title);
|
||||||
|
@ -189,7 +189,8 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
// article should be federated to alpha
|
// article should be federated to alpha
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleData {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
instance_id: Some(beta_instance.id),
|
// TODO: this is wrong
|
||||||
|
instance_domain: Some(beta_instance.ap_id.to_string()),
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
let get_res = data.alpha.get_article(get_article_data.clone()).await?;
|
let get_res = data.alpha.get_article(get_article_data.clone()).await?;
|
||||||
|
@ -243,7 +244,8 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
// article should be federated to alpha and gamma
|
// article should be federated to alpha and gamma
|
||||||
let get_article_data_alpha = GetArticleData {
|
let get_article_data_alpha = GetArticleData {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
instance_id: Some(beta_id_on_alpha.id),
|
// TODO: wrong
|
||||||
|
instance_domain: Some(beta_id_on_alpha.ap_id.to_string()),
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
let get_res = data
|
let get_res = data
|
||||||
|
@ -256,7 +258,8 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
|
|
||||||
let get_article_data_gamma = GetArticleData {
|
let get_article_data_gamma = GetArticleData {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
instance_id: Some(beta_id_on_gamma.id),
|
// TODO: wrong
|
||||||
|
instance_domain: Some(beta_id_on_gamma.ap_id.to_string()),
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
let get_res = data
|
let get_res = data
|
||||||
|
@ -383,7 +386,8 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
// alpha edits article
|
// alpha edits article
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleData {
|
||||||
title: Some(create_form.title.to_string()),
|
title: Some(create_form.title.to_string()),
|
||||||
instance_id: Some(beta_id_on_alpha.id),
|
// TODO: wrong
|
||||||
|
instance_domain: Some(beta_id_on_alpha.ap_id.to_string()),
|
||||||
id: None,
|
id: None,
|
||||||
};
|
};
|
||||||
let get_res = data.alpha.get_article(get_article_data).await?;
|
let get_res = data.alpha.get_article(get_article_data).await?;
|
||||||
|
|
Loading…
Reference in a new issue