mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-04 18:51: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,
|
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(()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue