add list of articles with local/all selection

This commit is contained in:
Felix Ableitner 2024-02-01 11:34:09 +01:00
parent a12895f9bf
commit 4e53b13b70
13 changed files with 112 additions and 24 deletions

29
Cargo.lock generated
View File

@ -1523,6 +1523,7 @@ dependencies = [
"url", "url",
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
"web-sys",
] ]
[[package]] [[package]]
@ -1653,9 +1654,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.65" version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -3692,9 +3693,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.89" version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -3702,9 +3703,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.89" version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@ -3729,9 +3730,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.89" version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -3739,9 +3740,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.89" version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3752,9 +3753,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.89" version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
[[package]] [[package]]
name = "wasm-streams" name = "wasm-streams"
@ -3771,9 +3772,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.65" version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",

View File

@ -68,6 +68,7 @@ console_log = "1.0.0"
time = "0.3.31" time = "0.3.31"
tower = "0.4.13" tower = "0.4.13"
markdown-it = "0.6.0" markdown-it = "0.6.0"
web-sys = "0.3.67"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"

View File

@ -6,12 +6,12 @@ use crate::backend::error::MyResult;
use crate::backend::federation::activities::create_article::CreateArticle; use crate::backend::federation::activities::create_article::CreateArticle;
use crate::backend::federation::activities::submit_article_update; use crate::backend::federation::activities::submit_article_update;
use crate::backend::utils::generate_article_version; use crate::backend::utils::generate_article_version;
use crate::common::GetArticleData;
use crate::common::LocalUserView; use crate::common::LocalUserView;
use crate::common::{ApiConflict, ResolveObject}; use crate::common::{ApiConflict, ResolveObject};
use crate::common::{ArticleView, DbArticle, DbEdit}; use crate::common::{ArticleView, DbArticle, DbEdit};
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData}; use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
use crate::common::{DbInstance, SearchArticleData}; use crate::common::{DbInstance, SearchArticleData};
use crate::common::{GetArticleData, ListArticlesData};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use anyhow::anyhow; use anyhow::anyhow;
@ -30,6 +30,10 @@ pub(in crate::backend::api) async fn create_article(
data: Data<MyDataHandle>, data: Data<MyDataHandle>,
Form(create_article): Form<CreateArticleData>, Form(create_article): Form<CreateArticleData>,
) -> MyResult<Json<ArticleView>> { ) -> MyResult<Json<ArticleView>> {
if create_article.title.is_empty() {
return Err(anyhow!("Title must not be empty").into());
}
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
let ap_id = ObjectId::parse(&format!( let ap_id = ObjectId::parse(&format!(
"http://{}:{}/article/{}", "http://{}:{}/article/{}",
@ -147,6 +151,15 @@ pub(in crate::backend::api) async fn get_article(
} }
} }
#[debug_handler]
pub(in crate::backend::api) async fn list_articles(
Query(query): Query<ListArticlesData>,
data: Data<MyDataHandle>,
) -> MyResult<Json<Vec<DbArticle>>> {
let only_local = query.only_local.unwrap_or(false);
Ok(Json(DbArticle::read_all(only_local, &data.db_connection)?))
}
/// 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
/// how an article should be edited. /// how an article should be edited.
#[debug_handler] #[debug_handler]

View File

@ -1,4 +1,6 @@
use crate::backend::api::article::{create_article, resolve_article, search_article}; use crate::backend::api::article::{
create_article, list_articles, resolve_article, search_article,
};
use crate::backend::api::article::{edit_article, fork_article, get_article}; use crate::backend::api::article::{edit_article, fork_article, get_article};
use crate::backend::api::instance::get_local_instance; use crate::backend::api::instance::get_local_instance;
use crate::backend::api::instance::{follow_instance, resolve_instance}; use crate::backend::api::instance::{follow_instance, resolve_instance};
@ -36,6 +38,7 @@ pub fn api_routes() -> Router {
"/article", "/article",
get(get_article).post(create_article).patch(edit_article), get(get_article).post(create_article).patch(edit_article),
) )
.route("/article/list", get(list_articles))
.route("/article/fork", post(fork_article)) .route("/article/fork", post(fork_article))
.route("/article/resolve", get(resolve_article)) .route("/article/resolve", get(resolve_article))
.route("/edit_conflicts", get(edit_conflicts)) .route("/edit_conflicts", get(edit_conflicts))

View File

@ -118,11 +118,16 @@ impl DbArticle {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_all_local(conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn read_all(only_local: bool, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(article::table let query = article::table.into_boxed();
Ok(if only_local {
query
.filter(article::dsl::local.eq(true)) .filter(article::dsl::local.eq(true))
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?
} else {
query.get_results(conn.deref_mut())?
})
} }
pub fn search(query: &str, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn search(query: &str, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {

View File

@ -37,7 +37,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_local(&data.db_connection)?; let local_articles = DbArticle::read_all(true, &data.db_connection)?;
let articles = future::try_join_all( let articles = future::try_join_all(
local_articles local_articles
.into_iter() .into_iter()

View File

@ -21,6 +21,11 @@ pub struct GetArticleData {
pub id: Option<i32>, pub id: Option<i32>,
} }
#[derive(Deserialize, Serialize, Clone)]
pub struct ListArticlesData {
pub only_local: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))] #[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))]

View File

@ -1,5 +1,5 @@
use crate::common::ApiConflict;
use crate::common::ResolveObject; use crate::common::ResolveObject;
use crate::common::{ApiConflict, ListArticlesData};
use crate::common::{ArticleView, LoginUserData, RegisterUserData}; use crate::common::{ArticleView, LoginUserData, RegisterUserData};
use crate::common::{CreateArticleData, EditArticleData, ForkArticleData, LocalUserView}; use crate::common::{CreateArticleData, EditArticleData, ForkArticleData, LocalUserView};
use crate::common::{DbArticle, GetArticleData}; use crate::common::{DbArticle, GetArticleData};
@ -36,8 +36,11 @@ impl ApiClient {
} }
pub async fn get_article(&self, data: GetArticleData) -> MyResult<ArticleView> { pub async fn get_article(&self, data: GetArticleData) -> MyResult<ArticleView> {
self.get_query::<ArticleView, _>("article", Some(data)) self.get_query("article", Some(data)).await
.await }
pub async fn list_articles(&self, data: ListArticlesData) -> MyResult<Vec<DbArticle>> {
self.get_query("article/list", Some(data)).await
} }
pub async fn register(&self, register_form: RegisterUserData) -> MyResult<LocalUserView> { pub async fn register(&self, register_form: RegisterUserData) -> MyResult<LocalUserView> {

View File

@ -4,6 +4,7 @@ use crate::frontend::components::nav::Nav;
use crate::frontend::pages::article::create::CreateArticle; use crate::frontend::pages::article::create::CreateArticle;
use crate::frontend::pages::article::edit::EditArticle; use crate::frontend::pages::article::edit::EditArticle;
use crate::frontend::pages::article::history::ArticleHistory; use crate::frontend::pages::article::history::ArticleHistory;
use crate::frontend::pages::article::list::ListArticles;
use crate::frontend::pages::article::read::ReadArticle; use crate::frontend::pages::article::read::ReadArticle;
use crate::frontend::pages::diff::EditDiff; use crate::frontend::pages::diff::EditDiff;
use crate::frontend::pages::login::Login; use crate::frontend::pages::login::Login;
@ -76,6 +77,7 @@ pub fn App() -> impl IntoView {
<Route path="/article/:title/history" view=ArticleHistory/> <Route path="/article/:title/history" view=ArticleHistory/>
<Route path="/article/:title/diff/:hash" view=EditDiff/> <Route path="/article/:title/diff/:hash" view=EditDiff/>
<Route path="/article/create" view=CreateArticle/> <Route path="/article/create" view=CreateArticle/>
<Route path="/article/list" view=ListArticles/>
<Route path={Page::Login.path()} view=Login/> <Route path={Page::Login.path()} view=Login/>
<Route path={Page::Register.path()} view=Register/> <Route path={Page::Register.path()} view=Register/>
<Route path="/search" view=Search/> <Route path="/search" view=Search/>

View File

@ -18,6 +18,9 @@ pub fn Nav() -> impl IntoView {
<li> <li>
<A href="/">"Main Page"</A> <A href="/">"Main Page"</A>
</li> </li>
<li>
<A href="/article/list">"List Articles"</A>
</li>
<Show <Show
when=move || global_state.with(|state| state.my_profile.is_some())> when=move || global_state.with(|state| state.my_profile.is_some())>
<li> <li>

View File

@ -0,0 +1,45 @@
use crate::common::ListArticlesData;
use crate::frontend::app::GlobalState;
use leptos::*;
use web_sys::wasm_bindgen::JsCast;
#[component]
pub fn ListArticles() -> impl IntoView {
let (only_local, set_only_local) = create_signal(true);
let articles = create_resource(
move || only_local.get(),
|only_local| async move {
GlobalState::api_client()
.list_articles(ListArticlesData {
only_local: Some(only_local),
})
.await
.unwrap()
},
);
view! {
<Suspense fallback=|| view! { "Loading..." }>
<fieldset on:input=move |ev| {
let val = ev
.target()
.unwrap()
.unchecked_into::<web_sys::HtmlInputElement>()
.id();
let is_local_only = val == "only-local";
set_only_local.update(|p| *p = is_local_only);
}>
<input type="radio" name="listing-type" id="only-local" checked />
<label for="only-local">Only Local</label>
<input type="radio" name="listing-type" id="all"/>
<label for="all">All</label>
</fieldset>
<ul> {
move || articles.get().map(|a|
a.into_iter().map(|a| view! {
<li><a href=format!("/article/{}", a.title)>{a.title()}</a></li>
}).collect::<Vec<_>>())
} </ul>
</Suspense>
}
}

View File

@ -1,4 +1,5 @@
pub mod create; pub mod create;
pub mod edit; pub mod edit;
pub mod history; pub mod history;
pub mod list;
pub mod read; pub mod read;

View File

@ -59,6 +59,12 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
assert_eq!(1, search_res.len()); assert_eq!(1, search_res.len());
assert_eq!(edit_res.article, search_res[0]); assert_eq!(edit_res.article, search_res[0]);
let list_articles = data.alpha.list_articles().await?;
dbg!(&list_articles);
// default main page and article created by this test
assert_eq!(2, list_articles.len());
assert_eq!(edit_res.article, list_articles[1]);
data.stop() data.stop()
} }