mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-03 17:11:08 +00:00
Working actions on notification page
This commit is contained in:
parent
65a10d471a
commit
2bd2780939
9 changed files with 117 additions and 30 deletions
|
@ -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<Json<Option<ApiConflict>>> {
|
||||
// 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<LocalUserView>,
|
||||
data: Data<IbisData>,
|
||||
Form(approve_params): Form<ApproveArticleForm>,
|
||||
) -> MyResult<Json<DbArticle>> {
|
||||
Form(params): Form<ApproveArticleForm>,
|
||||
) -> MyResult<Json<()>> {
|
||||
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<LocalUserView>,
|
||||
data: Data<IbisData>,
|
||||
Form(params): Form<DeleteConflictForm>,
|
||||
) -> MyResult<Json<()>> {
|
||||
DbConflict::delete(params.conflict_id, user.person.id, &data)?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -87,6 +87,11 @@ impl DbArticle {
|
|||
.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> {
|
||||
let mut conn = data.db_pool.get()?;
|
||||
let query = article::table.find(id).into_boxed();
|
||||
|
|
|
@ -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<Self> {
|
||||
/// Delete merge conflict which was created by specific user
|
||||
pub fn delete(id: ConflictId, creator_id: PersonId, data: &IbisData) -> MyResult<Self> {
|
||||
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>> {
|
||||
|
@ -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) => {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<DbArticle> {
|
||||
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<Vec<DbArticle>> {
|
||||
self.get_query("/api/v1/search", Some(search_form)).await
|
||||
}
|
||||
|
|
|
@ -59,13 +59,8 @@ impl<T: Default> DefaultResource<T> 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();
|
||||
|
||||
|
|
|
@ -14,17 +14,18 @@ pub fn Notifications() -> impl IntoView {
|
|||
view! {
|
||||
<h1 class="text-4xl font-bold font-serif my-6 grow flex-auto">Notifications</h1>
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
<ul>
|
||||
<ul class="divide-y divide-solid">
|
||||
{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
|
||||
<li>
|
||||
<li class="py-2">
|
||||
<a class="link text-lg" href=link>
|
||||
{title}
|
||||
</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>
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue