1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-12-04 08:31:09 +00:00

Working actions on notification page

This commit is contained in:
Felix Ableitner 2024-11-14 16:42:49 +01:00
parent 65a10d471a
commit 2bd2780939
9 changed files with 117 additions and 30 deletions

View file

@ -21,6 +21,7 @@ use crate::{
DbArticle, DbArticle,
DbEdit, DbEdit,
DbInstance, DbInstance,
DeleteConflictForm,
EditArticleForm, EditArticleForm,
EditVersion, EditVersion,
ForkArticleForm, ForkArticleForm,
@ -106,7 +107,7 @@ pub(in crate::backend::api) async fn edit_article(
) -> MyResult<Json<Option<ApiConflict>>> { ) -> MyResult<Json<Option<ApiConflict>>> {
// resolve conflict if any // resolve conflict if any
if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id { 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)?; let original_article = DbArticle::read_view(edit_form.article_id, &data)?;
if edit_form.new_text == original_article.article.text { 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( pub async fn approve_article(
Extension(user): Extension<LocalUserView>, Extension(user): Extension<LocalUserView>,
data: Data<IbisData>, data: Data<IbisData>,
Form(approve_params): Form<ApproveArticleForm>, Form(params): Form<ApproveArticleForm>,
) -> MyResult<Json<DbArticle>> { ) -> MyResult<Json<()>> {
check_is_admin(&user)?; check_is_admin(&user)?;
let article = DbArticle::update_approved(approve_params.article_id, true, &data)?; if params.approve {
Ok(Json(article)) 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<LocalUserView>,
data: Data<IbisData>,
Form(params): Form<DeleteConflictForm>,
) -> MyResult<Json<()>> {
DbConflict::delete(params.conflict_id, user.person.id, &data)?;
Ok(Json(()))
} }

View file

@ -21,13 +21,13 @@ use crate::{
}; };
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use anyhow::anyhow; use anyhow::anyhow;
use article::approve_article; use article::{approve_article, delete_conflict};
use axum::{ use axum::{
body::Body, body::Body,
http::{Request, StatusCode}, http::{Request, StatusCode},
middleware::{self, Next}, middleware::{self, Next},
response::Response, response::Response,
routing::{get, post}, routing::{delete, get, post},
Extension, Extension,
Json, Json,
Router, Router,
@ -52,6 +52,7 @@ pub fn api_routes() -> Router<()> {
.route("/article/resolve", get(resolve_article)) .route("/article/resolve", get(resolve_article))
.route("/article/protect", post(protect_article)) .route("/article/protect", post(protect_article))
.route("/article/approve", post(approve_article)) .route("/article/approve", post(approve_article))
.route("/conflict", delete(delete_conflict))
.route("/instance", get(get_instance)) .route("/instance", get(get_instance))
.route("/instance/follow", post(follow_instance)) .route("/instance/follow", post(follow_instance))
.route("/instance/resolve", get(resolve_instance)) .route("/instance/resolve", get(resolve_instance))

View file

@ -87,6 +87,11 @@ impl DbArticle {
.get_result::<Self>(conn.deref_mut())?) .get_result::<Self>(conn.deref_mut())?)
} }
pub fn delete(id: ArticleId, data: &IbisData) -> MyResult<Self> {
let mut conn = data.db_pool.get()?;
Ok(diesel::delete(article::dsl::article.find(id)).get_result::<Self>(conn.deref_mut())?)
}
pub fn read_view(id: ArticleId, data: &IbisData) -> MyResult<ArticleView> { pub fn read_view(id: ArticleId, data: &IbisData) -> MyResult<ArticleView> {
let mut conn = data.db_pool.get()?; let mut conn = data.db_pool.get()?;
let query = article::table.find(id).into_boxed(); let query = article::table.find(id).into_boxed();

View file

@ -72,10 +72,15 @@ impl DbConflict {
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
/// Delete a merge conflict after it is resolved. /// Delete merge conflict which was created by specific user
pub fn delete(id: ConflictId, data: &IbisData) -> MyResult<Self> { pub fn delete(id: ConflictId, creator_id: PersonId, data: &IbisData) -> MyResult<Self> {
let mut conn = data.db_pool.get()?; 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<IbisData>) -> MyResult<Option<ApiConflict>> { pub async fn to_api_conflict(&self, data: &Data<IbisData>) -> MyResult<Option<ApiConflict>> {
@ -103,7 +108,7 @@ impl DbConflict {
data, data,
) )
.await?; .await?;
DbConflict::delete(self.id, data)?; DbConflict::delete(self.id, self.creator_id, data)?;
Ok(None) Ok(None)
} }
Err(three_way_merge) => { Err(three_way_merge) => {

View file

@ -226,6 +226,12 @@ pub struct ForkArticleForm {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ApproveArticleForm { pub struct ApproveArticleForm {
pub article_id: ArticleId, pub article_id: ArticleId,
pub approve: bool,
}
#[derive(Deserialize, Serialize)]
pub struct DeleteConflictForm {
pub conflict_id: ConflictId,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
common::{ common::{
newtypes::ArticleId, newtypes::{ArticleId, ConflictId},
utils::http_protocol_str, utils::http_protocol_str,
ApiConflict, ApiConflict,
ApproveArticleForm, ApproveArticleForm,
@ -9,6 +9,7 @@ use crate::{
DbArticle, DbArticle,
DbInstance, DbInstance,
DbPerson, DbPerson,
DeleteConflictForm,
EditArticleForm, EditArticleForm,
FollowInstance, FollowInstance,
ForkArticleForm, ForkArticleForm,
@ -151,8 +152,11 @@ impl ApiClient {
handle_json_res(req).await handle_json_res(req).await
} }
pub async fn approve_article(&self, article_id: ArticleId) -> MyResult<DbArticle> { pub async fn approve_article(&self, article_id: ArticleId, approve: bool) -> MyResult<()> {
let form = ApproveArticleForm { article_id }; let form = ApproveArticleForm {
article_id,
approve,
};
let req = self let req = self
.client .client
.post(self.request_endpoint("/api/v1/article/approve")) .post(self.request_endpoint("/api/v1/article/approve"))
@ -160,6 +164,15 @@ impl ApiClient {
handle_json_res(req).await 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<Vec<DbArticle>> { pub async fn search(&self, search_form: &SearchArticleForm) -> MyResult<Vec<DbArticle>> {
self.get_query("/api/v1/search", Some(search_form)).await self.get_query("/api/v1/search", Some(search_form)).await
} }

View file

@ -59,13 +59,8 @@ impl<T: Default> DefaultResource<T> for Resource<(), T> {
#[component] #[component]
pub fn App() -> impl IntoView { pub fn App() -> impl IntoView {
// TODO: should create_resource() but then things break // TODO: should create_resource() but then things break
let site_resource = create_local_resource( let site_resource =
move || (), create_local_resource(move || (), |_| async move { CLIENT.site().await.unwrap() });
|_| async move {
let site = CLIENT.site().await.unwrap();
site
},
);
provide_context(site_resource); provide_context(site_resource);
provide_meta_context(); provide_meta_context();

View file

@ -14,17 +14,18 @@ pub fn Notifications() -> impl IntoView {
view! { view! {
<h1 class="text-4xl font-bold font-serif my-6 grow flex-auto">Notifications</h1> <h1 class="text-4xl font-bold font-serif my-6 grow flex-auto">Notifications</h1>
<Suspense fallback=|| view! { "Loading..." }> <Suspense fallback=|| view! { "Loading..." }>
<ul> <ul class="divide-y divide-solid">
{move || { {move || {
notifications notifications
.get() .get()
.map(|n| { .map(|n| {
n.into_iter() n.into_iter()
.map(|n| { .map(|ref notif| {
use Notification::*; use Notification::*;
let (link, title) = match n { let (my_style, link, title) = match notif {
EditConflict(c) => { EditConflict(c) => {
( (
"visibility: hidden",
format!("{}/edit/{}", article_link(&c.article), c.id.0), format!("{}/edit/{}", article_link(&c.article), c.id.0),
format!( format!(
"Conflict: {} - {}", "Conflict: {} - {}",
@ -35,17 +36,57 @@ pub fn Notifications() -> impl IntoView {
} }
ArticleApprovalRequired(a) => { ArticleApprovalRequired(a) => {
( (
article_link(&a), "",
article_link(a),
format!("Approval required: {}", a.title), 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! { view! {
// TODO: need buttons to approve/reject new article, also makes sense to discard edit conflict <li class="py-2">
<li>
<a class="link text-lg" href=link> <a class="link text-lg" href=link>
{title} {title}
</a> </a>
<div class="card-actions mt-2">
<button
class="btn btn-sm btn-outline"
style=my_style
on:click=move |_| click_approve.dispatch(())
>
Approve
</button>
<button
class="btn btn-sm btn-outline"
on:click=move |_| click_reject.dispatch(())
>
Reject
</button>
</div>
</li> </li>
} }
}) })

View file

@ -767,9 +767,14 @@ async fn test_article_approval_required() -> MyResult<()> {
}; };
assert_eq!(create_res.article.id, notif.id); assert_eq!(create_res.article.id, notif.id);
let approve = data.alpha.approve_article(notif.id).await?; data.alpha.approve_article(notif.id, true).await?;
assert_eq!(create_res.article.id, approve.id); let form = GetArticleForm {
assert!(approve.approved); 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?; let list_all = data.alpha.list_articles(Default::default()).await?;
assert_eq!(2, list_all.len()); assert_eq!(2, list_all.len());