diff --git a/src/backend/api/article.rs b/src/backend/api/article.rs index 3c377e1..9bc9248 100644 --- a/src/backend/api/article.rs +++ b/src/backend/api/article.rs @@ -21,6 +21,7 @@ use crate::{ DbArticle, DbEdit, DbInstance, + DeleteConflictForm, EditArticleForm, EditVersion, ForkArticleForm, @@ -106,7 +107,7 @@ pub(in crate::backend::api) async fn edit_article( ) -> MyResult>> { // resolve conflict if any if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id { - DbConflict::delete(resolve_conflict_id, &data)?; + DbConflict::delete(resolve_conflict_id, user.person.id, &data)?; } let original_article = DbArticle::read_view(edit_form.article_id, &data)?; if edit_form.new_text == original_article.article.text { @@ -297,9 +298,24 @@ pub(in crate::backend::api) async fn protect_article( pub async fn approve_article( Extension(user): Extension, data: Data, - Form(approve_params): Form, -) -> MyResult> { + Form(params): Form, +) -> MyResult> { check_is_admin(&user)?; - let article = DbArticle::update_approved(approve_params.article_id, true, &data)?; - Ok(Json(article)) + if params.approve { + DbArticle::update_approved(params.article_id, true, &data)?; + } else { + DbArticle::delete(params.article_id, &data)?; + } + Ok(Json(())) +} + +/// Get a list of all unresolved edit conflicts. +#[debug_handler] +pub async fn delete_conflict( + Extension(user): Extension, + data: Data, + Form(params): Form, +) -> MyResult> { + DbConflict::delete(params.conflict_id, user.person.id, &data)?; + Ok(Json(())) } diff --git a/src/backend/api/mod.rs b/src/backend/api/mod.rs index d1e5432..4864997 100644 --- a/src/backend/api/mod.rs +++ b/src/backend/api/mod.rs @@ -21,13 +21,13 @@ use crate::{ }; use activitypub_federation::config::Data; use anyhow::anyhow; -use article::approve_article; +use article::{approve_article, delete_conflict}; use axum::{ body::Body, http::{Request, StatusCode}, middleware::{self, Next}, response::Response, - routing::{get, post}, + routing::{delete, get, post}, Extension, Json, Router, @@ -52,6 +52,7 @@ pub fn api_routes() -> Router<()> { .route("/article/resolve", get(resolve_article)) .route("/article/protect", post(protect_article)) .route("/article/approve", post(approve_article)) + .route("/conflict", delete(delete_conflict)) .route("/instance", get(get_instance)) .route("/instance/follow", post(follow_instance)) .route("/instance/resolve", get(resolve_instance)) diff --git a/src/backend/database/article.rs b/src/backend/database/article.rs index a7fd939..56aa437 100644 --- a/src/backend/database/article.rs +++ b/src/backend/database/article.rs @@ -87,6 +87,11 @@ impl DbArticle { .get_result::(conn.deref_mut())?) } + pub fn delete(id: ArticleId, data: &IbisData) -> MyResult { + let mut conn = data.db_pool.get()?; + Ok(diesel::delete(article::dsl::article.find(id)).get_result::(conn.deref_mut())?) + } + pub fn read_view(id: ArticleId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; let query = article::table.find(id).into_boxed(); diff --git a/src/backend/database/conflict.rs b/src/backend/database/conflict.rs index 43d85a1..5c39bf3 100644 --- a/src/backend/database/conflict.rs +++ b/src/backend/database/conflict.rs @@ -72,10 +72,15 @@ impl DbConflict { .get_results(conn.deref_mut())?) } - /// Delete a merge conflict after it is resolved. - pub fn delete(id: ConflictId, data: &IbisData) -> MyResult { + /// Delete merge conflict which was created by specific user + pub fn delete(id: ConflictId, creator_id: PersonId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; - Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?) + Ok(delete( + conflict::table + .filter(conflict::dsl::creator_id.eq(creator_id)) + .find(id), + ) + .get_result(conn.deref_mut())?) } pub async fn to_api_conflict(&self, data: &Data) -> MyResult> { @@ -103,7 +108,7 @@ impl DbConflict { data, ) .await?; - DbConflict::delete(self.id, data)?; + DbConflict::delete(self.id, self.creator_id, data)?; Ok(None) } Err(three_way_merge) => { diff --git a/src/common/mod.rs b/src/common/mod.rs index b71217f..a7b9ae8 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -226,6 +226,12 @@ pub struct ForkArticleForm { #[derive(Deserialize, Serialize)] pub struct ApproveArticleForm { pub article_id: ArticleId, + pub approve: bool, +} + +#[derive(Deserialize, Serialize)] +pub struct DeleteConflictForm { + pub conflict_id: ConflictId, } #[derive(Deserialize, Serialize, Debug)] diff --git a/src/frontend/api.rs b/src/frontend/api.rs index 3fc5d06..9058e50 100644 --- a/src/frontend/api.rs +++ b/src/frontend/api.rs @@ -1,6 +1,6 @@ use crate::{ common::{ - newtypes::ArticleId, + newtypes::{ArticleId, ConflictId}, utils::http_protocol_str, ApiConflict, ApproveArticleForm, @@ -9,6 +9,7 @@ use crate::{ DbArticle, DbInstance, DbPerson, + DeleteConflictForm, EditArticleForm, FollowInstance, ForkArticleForm, @@ -151,8 +152,11 @@ impl ApiClient { handle_json_res(req).await } - pub async fn approve_article(&self, article_id: ArticleId) -> MyResult { - let form = ApproveArticleForm { article_id }; + pub async fn approve_article(&self, article_id: ArticleId, approve: bool) -> MyResult<()> { + let form = ApproveArticleForm { + article_id, + approve, + }; let req = self .client .post(self.request_endpoint("/api/v1/article/approve")) @@ -160,6 +164,15 @@ impl ApiClient { handle_json_res(req).await } + pub async fn delete_conflict(&self, conflict_id: ConflictId) -> MyResult<()> { + let form = DeleteConflictForm { conflict_id }; + let req = self + .client + .delete(self.request_endpoint("/api/v1/conflict")) + .form(&form); + handle_json_res(req).await + } + pub async fn search(&self, search_form: &SearchArticleForm) -> MyResult> { self.get_query("/api/v1/search", Some(search_form)).await } diff --git a/src/frontend/app.rs b/src/frontend/app.rs index 726f072..4c999e7 100644 --- a/src/frontend/app.rs +++ b/src/frontend/app.rs @@ -59,13 +59,8 @@ impl DefaultResource for Resource<(), T> { #[component] pub fn App() -> impl IntoView { // TODO: should create_resource() but then things break - let site_resource = create_local_resource( - move || (), - |_| async move { - let site = CLIENT.site().await.unwrap(); - site - }, - ); + let site_resource = + create_local_resource(move || (), |_| async move { CLIENT.site().await.unwrap() }); provide_context(site_resource); provide_meta_context(); diff --git a/src/frontend/pages/notifications.rs b/src/frontend/pages/notifications.rs index d4597be..70da2ca 100644 --- a/src/frontend/pages/notifications.rs +++ b/src/frontend/pages/notifications.rs @@ -14,17 +14,18 @@ pub fn Notifications() -> impl IntoView { view! {

Notifications

-
    +
      {move || { notifications .get() .map(|n| { n.into_iter() - .map(|n| { + .map(|ref notif| { use Notification::*; - let (link, title) = match n { + let (my_style, link, title) = match notif { EditConflict(c) => { ( + "visibility: hidden", format!("{}/edit/{}", article_link(&c.article), c.id.0), format!( "Conflict: {} - {}", @@ -35,17 +36,57 @@ pub fn Notifications() -> impl IntoView { } ArticleApprovalRequired(a) => { ( - article_link(&a), + "", + article_link(a), format!("Approval required: {}", a.title), ) } }; + let notif_ = notif.clone(); + let click_approve = create_action(move |_: &()| { + let notif_ = notif_.clone(); + async move { + if let ArticleApprovalRequired(a) = notif_ { + CLIENT.approve_article(a.id, true).await.unwrap(); + } + notifications.refetch(); + } + }); + let notif_ = notif.clone(); + let click_reject = create_action(move |_: &()| { + let notif_ = notif_.clone(); + async move { + match notif_ { + EditConflict(c) => { + CLIENT.delete_conflict(c.id).await.unwrap(); + } + ArticleApprovalRequired(a) => { + CLIENT.approve_article(a.id, false).await.unwrap(); + } + } + notifications.refetch(); + } + }); view! { - // TODO: need buttons to approve/reject new article, also makes sense to discard edit conflict -
    • +
    • {title} +
      + + +
    • } }) diff --git a/tests/test.rs b/tests/test.rs index c8be46a..ef8ec4c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -767,9 +767,14 @@ async fn test_article_approval_required() -> MyResult<()> { }; assert_eq!(create_res.article.id, notif.id); - let approve = data.alpha.approve_article(notif.id).await?; - assert_eq!(create_res.article.id, approve.id); - assert!(approve.approved); + data.alpha.approve_article(notif.id, true).await?; + let form = GetArticleForm { + id: Some(create_res.article.id), + ..Default::default() + }; + let approved = data.alpha.get_article(form).await?; + assert_eq!(create_res.article.id, approved.article.id); + assert!(approved.article.approved); let list_all = data.alpha.list_articles(Default::default()).await?; assert_eq!(2, list_all.len());