diff --git a/src/backend/api/article.rs b/src/backend/api/article.rs index dc99d64..fff62cd 100644 --- a/src/backend/api/article.rs +++ b/src/backend/api/article.rs @@ -42,7 +42,7 @@ use diffy::create_patch; /// Create a new article with empty text, and federate it to followers. #[debug_handler] pub(in crate::backend::api) async fn create_article( - Extension(user): Extension, + Extension(mut user): Extension, data: Data, Form(create_article): Form, ) -> MyResult> { @@ -79,9 +79,13 @@ pub(in crate::backend::api) async fn create_article( previous_version_id: article.latest_edit_version(&data)?, resolve_conflict_id: None, }; + + // workaround so the edit goes through + user.local_user.admin = true; let _ = edit_article(Extension(user), data.reset_request_count(), Form(edit_data)).await?; - let article_view = DbArticle::read_view(article.id, &data)?; + // allow reading unapproved article here + let article_view = DbArticle::read_view(article.id, true, &data)?; CreateArticle::send_to_followers(article_view.article.clone(), &data).await?; Ok(Json(article_view)) @@ -102,11 +106,12 @@ pub(in crate::backend::api) async fn edit_article( data: Data, Form(mut edit_form): Form, ) -> MyResult>> { + let is_admin = check_is_admin(&user).is_ok(); // resolve conflict if any if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id { DbConflict::delete(resolve_conflict_id, &data)?; } - let original_article = DbArticle::read_view(edit_form.article_id, &data)?; + let original_article = DbArticle::read_view(edit_form.article_id, is_admin, &data)?; if edit_form.new_text == original_article.article.text { return Err(anyhow!("Edit contains no changes").into()); } @@ -148,7 +153,7 @@ pub(in crate::backend::api) async fn edit_article( previous_version_id: previous_version.hash, }; let conflict = DbConflict::create(&form, &data)?; - Ok(Json(conflict.to_api_conflict(&data).await?)) + Ok(Json(conflict.to_api_conflict(is_admin, &data).await?)) } } @@ -156,19 +161,23 @@ pub(in crate::backend::api) async fn edit_article( #[debug_handler] pub(in crate::backend::api) async fn get_article( Query(query): Query, + Extension(user): Extension, data: Data, ) -> MyResult> { + let is_admin = check_is_admin(&user).is_ok(); match (query.title, query.id) { (Some(title), None) => Ok(Json(DbArticle::read_view_title( &title, query.domain, + is_admin, &data, )?)), (None, Some(id)) => { if query.domain.is_some() { return Err(anyhow!("Cant combine id and instance_domain").into()); } - Ok(Json(DbArticle::read_view(id, &data)?)) + let article = DbArticle::read_view(id, is_admin, &data)?; + Ok(Json(article)) } _ => Err(anyhow!("Must pass exactly one of title, id").into()), } @@ -190,12 +199,13 @@ pub(in crate::backend::api) async fn list_articles( /// how an article should be edited. #[debug_handler] pub(in crate::backend::api) async fn fork_article( - Extension(_user): Extension, + Extension(user): Extension, data: Data, Form(fork_form): Form, ) -> MyResult> { + let is_admin = check_is_admin(&user).is_ok(); // TODO: lots of code duplicated from create_article(), can move it into helper - let original_article = DbArticle::read(fork_form.article_id, &data)?; + let original_article = DbArticle::read_view(fork_form.article_id, is_admin, &data)?; let local_instance = DbInstance::read_local_instance(&data)?; let ap_id = ObjectId::parse(&format!( @@ -206,18 +216,19 @@ pub(in crate::backend::api) async fn fork_article( ))?; let form = DbArticleForm { title: fork_form.new_title, - text: original_article.text.clone(), + text: original_article.article.text.clone(), ap_id, instance_id: local_instance.id, local: true, protected: false, - approved: data.config.article_approval, + approved: !data.config.article_approval, }; let article = DbArticle::create(form, &data)?; // copy edits to new article // this could also be done in sql - let edits = DbEdit::read_for_article(&original_article, &data)? + let edits = original_article + .edits .into_iter() .map(|e| e.edit) .collect::>(); @@ -238,7 +249,7 @@ pub(in crate::backend::api) async fn fork_article( CreateArticle::send_to_followers(article.clone(), &data).await?; - Ok(Json(DbArticle::read_view(article.id, &data)?)) + Ok(Json(DbArticle::read_view(article.id, is_admin, &data)?)) } /// Fetch a remote article, including edits collection. Allows viewing and editing. Note that new @@ -288,17 +299,6 @@ pub(in crate::backend::api) async fn protect_article( Ok(Json(article)) } -/// Get a list of all unresolved edit conflicts. -#[debug_handler] -pub async fn list_approval_required( - Extension(user): Extension, - data: Data, -) -> MyResult>> { - check_is_admin(&user)?; - let articles = DbArticle::list_approval_required(&data)?; - Ok(Json(articles)) -} - /// Get a list of all unresolved edit conflicts. #[debug_handler] pub async fn approve_article( diff --git a/src/backend/api/mod.rs b/src/backend/api/mod.rs index 24f4cba..c0e216c 100644 --- a/src/backend/api/mod.rs +++ b/src/backend/api/mod.rs @@ -22,28 +22,25 @@ use crate::{ AUTH_COOKIE, }, }, - database::{conflict::DbConflict, IbisData}, + database::IbisData, error::MyResult, }, - common::{ApiConflict, LocalUserView}, + common::LocalUserView, }; use activitypub_federation::config::Data; use anyhow::anyhow; -use article::{approve_article, list_approval_required}; +use article::approve_article; use axum::{ body::Body, http::{Request, StatusCode}, middleware::{self, Next}, response::Response, routing::{get, post}, - Extension, - Json, Router, }; use axum_extra::extract::CookieJar; -use axum_macros::debug_handler; -use futures::future::try_join_all; use instance::list_remote_instances; +use user::{count_notifications, list_notifications}; pub mod article; pub mod instance; @@ -59,18 +56,15 @@ pub fn api_routes() -> Router<()> { .route("/article/fork", post(fork_article)) .route("/article/resolve", get(resolve_article)) .route("/article/protect", post(protect_article)) - .route( - "/article/list/approval_required", - get(list_approval_required), - ) .route("/article/approve", post(approve_article)) - .route("/edit_conflicts", get(edit_conflicts)) .route("/instance", get(get_instance)) .route("/instance/follow", post(follow_instance)) .route("/instance/resolve", get(resolve_instance)) .route("/instance/list", get(list_remote_instances)) .route("/search", get(search_article)) .route("/user", get(get_user)) + .route("/user/notifications/list", get(list_notifications)) + .route("/user/notifications/count", get(count_notifications)) .route("/account/register", post(register_user)) .route("/account/login", post(login_user)) .route("/account/my_profile", get(my_profile)) @@ -99,21 +93,3 @@ fn check_is_admin(user: &LocalUserView) -> MyResult<()> { } Ok(()) } - -/// Get a list of all unresolved edit conflicts. -#[debug_handler] -async fn edit_conflicts( - Extension(user): Extension, - data: Data, -) -> MyResult>> { - let conflicts = DbConflict::list(&user.person, &data)?; - let conflicts: Vec = try_join_all(conflicts.into_iter().map(|c| { - let data = data.reset_request_count(); - async move { c.to_api_conflict(&data).await } - })) - .await? - .into_iter() - .flatten() - .collect(); - Ok(Json(conflicts)) -} diff --git a/src/backend/api/user.rs b/src/backend/api/user.rs index 9c5d416..1c2b96c 100644 --- a/src/backend/api/user.rs +++ b/src/backend/api/user.rs @@ -1,17 +1,27 @@ +use super::check_is_admin; use crate::{ backend::{ - database::{read_jwt_secret, IbisData}, + database::{conflict::DbConflict, read_jwt_secret, IbisData}, error::MyResult, }, - common::{DbPerson, GetUserForm, LocalUserView, LoginUserForm, RegisterUserForm}, + common::{ + DbArticle, + DbPerson, + GetUserForm, + LocalUserView, + LoginUserForm, + Notification, + RegisterUserForm, + }, }; use activitypub_federation::config::Data; use anyhow::anyhow; -use axum::{extract::Query, Form, Json}; +use axum::{extract::Query, Extension, Form, Json}; use axum_extra::extract::cookie::{Cookie, CookieJar, Expiration, SameSite}; use axum_macros::debug_handler; use bcrypt::verify; use chrono::Utc; +use futures::future::try_join_all; use jsonwebtoken::{ decode, encode, @@ -145,3 +155,49 @@ pub(in crate::backend::api) async fn get_user( &data, )?)) } + +#[debug_handler] +pub(crate) async fn list_notifications( + Extension(user): Extension, + data: Data, +) -> MyResult>> { + let is_admin = check_is_admin(&user).is_ok(); + let conflicts = DbConflict::list(&user.person, &data)?; + let conflicts: Vec<_> = try_join_all(conflicts.into_iter().map(|c| { + let data = data.reset_request_count(); + async move { c.to_api_conflict(is_admin, &data).await } + })) + .await?; + let mut notifications: Vec<_> = conflicts + .into_iter() + .flatten() + .map(Notification::EditConflict) + .collect(); + + if check_is_admin(&user).is_ok() { + let articles = DbArticle::list_approval_required(&data)?; + notifications.extend( + articles + .into_iter() + .map(Notification::ArticleApprovalRequired), + ) + } + + Ok(Json(notifications)) +} + +#[debug_handler] +pub(crate) async fn count_notifications( + Extension(user): Extension, + data: Data, +) -> MyResult> { + let mut count = 0; + let conflicts = DbConflict::list(&user.person, &data)?; + count += conflicts.len(); + if check_is_admin(&user).is_ok() { + let articles = DbArticle::list_approval_required(&data)?; + count += articles.len(); + } + + Ok(Json(count)) +} diff --git a/src/backend/database/article.rs b/src/backend/database/article.rs index 1ad4567..887dbba 100644 --- a/src/backend/database/article.rs +++ b/src/backend/database/article.rs @@ -87,14 +87,13 @@ impl DbArticle { .get_result::(conn.deref_mut())?) } - pub fn read(id: ArticleId, data: &IbisData) -> MyResult { + pub fn read_view(id: ArticleId, is_admin: bool, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; - Ok(article::table.find(id).get_result(conn.deref_mut())?) - } - - pub fn read_view(id: ArticleId, data: &IbisData) -> MyResult { - let mut conn = data.db_pool.get()?; - let article: DbArticle = { article::table.find(id).get_result(conn.deref_mut())? }; + let mut query = article::table.find(id).into_boxed(); + if !is_admin { + query = query.filter(article::dsl::approved.eq(true)); + } + let article: DbArticle = query.get_result(conn.deref_mut())?; let latest_version = article.latest_edit_version(data)?; let edits = DbEdit::read_for_article(&article, data)?; Ok(ArticleView { @@ -107,6 +106,7 @@ impl DbArticle { pub fn read_view_title( title: &str, domain: Option, + admin: bool, data: &IbisData, ) -> MyResult { let mut conn = data.db_pool.get()?; @@ -115,11 +115,14 @@ impl DbArticle { .inner_join(instance::table) .filter(article::dsl::title.eq(title)) .into_boxed(); - let query = if let Some(domain) = domain { + let mut query = if let Some(domain) = domain { query.filter(instance::dsl::domain.eq(domain)) } else { query.filter(article::dsl::local.eq(true)) }; + if !admin { + query = query.filter(article::dsl::approved.eq(true)); + } query .select(article::all_columns) .get_result(conn.deref_mut())? @@ -140,14 +143,6 @@ impl DbArticle { .get_result(conn.deref_mut())?) } - pub fn read_local_title(title: &str, data: &IbisData) -> MyResult { - let mut conn = data.db_pool.get()?; - Ok(article::table - .filter(article::dsl::title.eq(title)) - .filter(article::dsl::local.eq(true)) - .get_result(conn.deref_mut())?) - } - /// Read all articles, ordered by most recently edited first. /// /// TODO: Should get rid of only_local param and rely on instance_id diff --git a/src/backend/database/conflict.rs b/src/backend/database/conflict.rs index 0f95793..9556a4d 100644 --- a/src/backend/database/conflict.rs +++ b/src/backend/database/conflict.rs @@ -76,10 +76,14 @@ impl DbConflict { Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?) } - pub async fn to_api_conflict(&self, data: &Data) -> MyResult> { - let article = DbArticle::read(self.article_id, data)?; + pub async fn to_api_conflict( + &self, + is_admin: bool, + data: &Data, + ) -> MyResult> { + let article = DbArticle::read_view(self.article_id, is_admin, data)?; // Make sure to get latest version from origin so that all conflicts can be resolved - let original_article = article.ap_id.dereference_forced(data).await?; + let original_article = article.article.ap_id.dereference_forced(data).await?; // create common ancestor version let edits = DbEdit::read_for_article(&original_article, data)?; diff --git a/src/backend/federation/objects/edit.rs b/src/backend/federation/objects/edit.rs index f8353de..b472e3d 100644 --- a/src/backend/federation/objects/edit.rs +++ b/src/backend/federation/objects/edit.rs @@ -52,7 +52,7 @@ impl Object for DbEdit { } async fn into_json(self, data: &Data) -> Result { - let article = DbArticle::read(self.article_id, data)?; + let article = DbArticle::read_view(self.article_id, false, data)?; let creator = DbPerson::read(self.creator_id, data)?; Ok(ApubEdit { kind: PatchType::Patch, @@ -61,7 +61,7 @@ impl Object for DbEdit { summary: self.summary, version: self.hash, previous_version: self.previous_version_id, - object: article.ap_id, + object: article.article.ap_id, attributed_to: creator.ap_id, published: self.created, }) diff --git a/src/backend/federation/objects/edits_collection.rs b/src/backend/federation/objects/edits_collection.rs index 3059958..3b96bba 100644 --- a/src/backend/federation/objects/edits_collection.rs +++ b/src/backend/federation/objects/edits_collection.rs @@ -35,7 +35,7 @@ impl Collection for DbEditCollection { owner: &Self::Owner, data: &Data, ) -> Result { - let article = DbArticle::read_view(owner.id, data)?; + let article = DbArticle::read_view(owner.id, false, data)?; let edits = future::try_join_all( article diff --git a/src/backend/federation/routes.rs b/src/backend/federation/routes.rs index 049534c..4647c04 100644 --- a/src/backend/federation/routes.rs +++ b/src/backend/federation/routes.rs @@ -94,8 +94,8 @@ async fn http_get_article( Path(title): Path, data: Data, ) -> MyResult>> { - let article = DbArticle::read_local_title(&title, &data)?; - let json = article.into_json(&data).await?; + let article = DbArticle::read_view_title(&title, None, false, &data)?; + let json = article.article.into_json(&data).await?; Ok(FederationJson(WithContext::new_default(json))) } @@ -104,8 +104,8 @@ async fn http_get_article_edits( Path(title): Path, data: Data, ) -> MyResult>> { - let article = DbArticle::read_local_title(&title, &data)?; - let json = DbEditCollection::read_local(&article, &data).await?; + let article = DbArticle::read_view_title(&title, None, false, &data)?; + let json = DbEditCollection::read_local(&article.article, &data).await?; Ok(FederationJson(WithContext::new_default(json))) } diff --git a/src/common/mod.rs b/src/common/mod.rs index 6c47fef..ce0d9c4 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -250,6 +250,12 @@ pub struct ApiConflict { pub previous_version_id: EditVersion, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Notification { + EditConflict(ApiConflict), + ArticleApprovalRequired(DbArticle), +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))] #[cfg_attr(feature = "ssr", diesel(table_name = instance, check_for_backend(diesel::pg::Pg)))] diff --git a/src/frontend/api.rs b/src/frontend/api.rs index 808c493..73ad047 100644 --- a/src/frontend/api.rs +++ b/src/frontend/api.rs @@ -19,6 +19,7 @@ use crate::{ ListArticlesForm, LocalUserView, LoginUserForm, + Notification, ProtectArticleForm, RegisterUserForm, ResolveObject, @@ -132,10 +133,17 @@ impl ApiClient { .await } - pub async fn list_articles_approval_required(&self) -> MyResult> { + pub async fn notifications_list(&self) -> MyResult> { let req = self .client - .get(self.request_endpoint("/api/v1/article/list/approval_required")); + .get(self.request_endpoint("/api/v1/user/notifications/list")); + handle_json_res(req).await + } + + pub async fn notifications_count(&self) -> MyResult { + let req = self + .client + .get(self.request_endpoint("/api/v1/user/notifications/count")); handle_json_res(req).await } @@ -230,13 +238,6 @@ impl ApiClient { handle_json_res(req).await } - pub async fn get_conflicts(&self) -> MyResult> { - let req = self - .client - .get(self.request_endpoint("/api/v1/edit_conflicts")); - Ok(handle_json_res(req).await.unwrap()) - } - pub async fn resolve_article(&self, id: Url) -> MyResult { let resolve_object = ResolveObject { id }; self.get_query("/api/v1/article/resolve", Some(resolve_object)) diff --git a/src/frontend/app.rs b/src/frontend/app.rs index 2ebcaa0..e5b8dae 100644 --- a/src/frontend/app.rs +++ b/src/frontend/app.rs @@ -13,10 +13,10 @@ use crate::{ list::ListArticles, read::ReadArticle, }, - conflicts::Conflicts, diff::EditDiff, instance::{details::InstanceDetails, list::ListInstances}, login::Login, + notifications::Notifications, register::Register, search::Search, user_profile::UserProfile, @@ -125,7 +125,7 @@ pub fn App() -> impl IntoView { - + diff --git a/src/frontend/components/nav.rs b/src/frontend/components/nav.rs index 509d83a..ab08dcb 100644 --- a/src/frontend/components/nav.rs +++ b/src/frontend/components/nav.rs @@ -19,6 +19,15 @@ pub fn Nav() -> impl IntoView { .unwrap_or_default() }, ); + let notification_count = create_resource( + || (), + move |_| async move { + GlobalState::api_client() + .notifications_count() + .await + .unwrap_or_default() + }, + ); let (search_query, set_search_query) = create_signal(String::new()); let mut dark_mode = expect_context::(); @@ -57,7 +66,7 @@ pub fn Nav() -> impl IntoView { "Create Article"
  • - "Edit Conflicts" + "Notifications "{notification_count}
  • diff --git a/src/frontend/pages/article/edit.rs b/src/frontend/pages/article/edit.rs index 3cd6bff..7427b2f 100644 --- a/src/frontend/pages/article/edit.rs +++ b/src/frontend/pages/article/edit.rs @@ -1,5 +1,5 @@ use crate::{ - common::{newtypes::ConflictId, ApiConflict, ArticleView, EditArticleForm}, + common::{newtypes::ConflictId, ApiConflict, ArticleView, EditArticleForm, Notification}, frontend::{ app::GlobalState, components::{ @@ -35,10 +35,14 @@ pub fn EditArticle() -> impl IntoView { let conflict_id = ConflictId(conflict_id.parse().unwrap()); async move { let conflict = GlobalState::api_client() - .get_conflicts() + .notifications_list() .await .unwrap() .into_iter() + .filter_map(|n| match n { + Notification::EditConflict(c) => Some(c), + _ => None, + }) .find(|c| c.id == conflict_id) .unwrap(); set_edit_response.set(EditResponse::Conflict(conflict)); diff --git a/src/frontend/pages/conflicts.rs b/src/frontend/pages/conflicts.rs deleted file mode 100644 index 483fb29..0000000 --- a/src/frontend/pages/conflicts.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::frontend::{app::GlobalState, article_link, article_title}; -use leptos::*; - -#[component] -pub fn Conflicts() -> impl IntoView { - let conflicts = create_resource( - move || {}, - |_| async move { GlobalState::api_client().get_conflicts().await.unwrap() }, - ); - - view! { -

    Your unresolved edit conflicts

    - -
      - {move || { - conflicts - .get() - .map(|c| { - c.into_iter() - .map(|c| { - let link = format!( - "{}/edit/{}", - article_link(&c.article), - c.id.0, - ); - view! { -
    • - - {article_title(&c.article)} " - " {c.summary} - -
    • - } - }) - .collect::>() - }) - }} - -
    -
    - } -} diff --git a/src/frontend/pages/mod.rs b/src/frontend/pages/mod.rs index 76e5080..8b9030c 100644 --- a/src/frontend/pages/mod.rs +++ b/src/frontend/pages/mod.rs @@ -6,10 +6,10 @@ use leptos::{create_resource, Resource, SignalGet}; use leptos_router::use_params_map; pub(crate) mod article; -pub(crate) mod conflicts; pub(crate) mod diff; pub(crate) mod instance; pub(crate) mod login; +pub(crate) mod notifications; pub(crate) mod register; pub(crate) mod search; pub(crate) mod user_profile; diff --git a/src/frontend/pages/notifications.rs b/src/frontend/pages/notifications.rs new file mode 100644 index 0000000..63cdabf --- /dev/null +++ b/src/frontend/pages/notifications.rs @@ -0,0 +1,55 @@ +use crate::{ + common::Notification, + frontend::{app::GlobalState, article_link, article_title}, +}; +use leptos::*; + +#[component] +pub fn Notifications() -> impl IntoView { + let notifications = create_local_resource( + move || {}, + |_| async move { + GlobalState::api_client() + .notifications_list() + .await + .unwrap() + }, + ); + + view! { +

    Notifications

    + +
      + {move || { + notifications + .get() + .map(|n| { + n.into_iter() + .map(|n| { + use Notification::*; + let (link, title) = match n { + EditConflict(c) => (format!( + "{}/edit/{}", + article_link(&c.article), + c.id.0) + , format!("Conflict: {} - {}", article_title(&c.article), c.summary)), + ArticleApprovalRequired(a) => (article_link(&a), format!("Approval required: {}", a.title)), + + }; + // TODO: need buttons to approve/reject new article, also makes sense to discard edit conflict + view! { +
    • + + {title} + +
    • + } + }) + .collect::>() + }) + }} + +
    +
    + } +} diff --git a/tests/common.rs b/tests/common.rs index 23b0266..f62b55f 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,7 +2,7 @@ use ibis::{ backend::{ - config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation, IbisConfigSetup}, + config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation}, start, }, common::RegisterUserForm, diff --git a/tests/test.rs b/tests/test.rs index b1215d3..73e847c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -14,6 +14,7 @@ use ibis::{ GetUserForm, ListArticlesForm, LoginUserForm, + Notification, ProtectArticleForm, RegisterUserForm, SearchArticleForm, @@ -377,9 +378,12 @@ async fn test_local_edit_conflict() -> MyResult<()> { .unwrap(); assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome\nexample\ntext\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge); - let conflicts = data.alpha.get_conflicts().await?; - assert_eq!(1, conflicts.len()); - assert_eq!(conflicts[0], edit_res); + let notifications = data.alpha.notifications_list().await?; + assert_eq!(1, notifications.len()); + let Notification::EditConflict(conflict) = ¬ifications[0] else { + panic!() + }; + assert_eq!(conflict, &edit_res); let edit_form = EditArticleForm { article_id: create_res.article.id, @@ -391,8 +395,7 @@ async fn test_local_edit_conflict() -> MyResult<()> { let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); - let conflicts = data.alpha.get_conflicts().await?; - assert_eq!(0, conflicts.len()); + assert_eq!(0, data.alpha.notifications_count().await?); data.stop() } @@ -463,23 +466,28 @@ async fn test_federated_edit_conflict() -> MyResult<()> { assert_eq!(1, edit_res.edits.len()); assert!(!edit_res.article.local); - let conflicts = data.gamma.get_conflicts().await?; - assert_eq!(1, conflicts.len()); + assert_eq!(1, data.gamma.notifications_count().await?); + let notifications = data.gamma.notifications_list().await?; + assert_eq!(1, notifications.len()); + let Notification::EditConflict(conflict) = ¬ifications[0] else { + panic!() + }; // resolve the conflict let edit_form = EditArticleForm { article_id: resolve_res.article.id, new_text: "aaaa\n".to_string(), summary: "summary".to_string(), - previous_version_id: conflicts[0].previous_version_id.clone(), - resolve_conflict_id: Some(conflicts[0].id), + previous_version_id: conflict.previous_version_id.clone(), + resolve_conflict_id: Some(conflict.id), }; let edit_res = data.gamma.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(3, edit_res.edits.len()); - let conflicts = data.gamma.get_conflicts().await?; - assert_eq!(0, conflicts.len()); + assert_eq!(0, data.gamma.notifications_count().await?); + let notifications = data.gamma.notifications_list().await?; + assert_eq!(0, notifications.len()); data.stop() } @@ -519,8 +527,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> { resolve_conflict_id: None, }; let edit_res = data.alpha.edit_article(&edit_form).await?; - let conflicts = data.alpha.get_conflicts().await?; - assert_eq!(0, conflicts.len()); + assert_eq!(0, data.alpha.notifications_count().await?); assert_eq!(3, edit_res.edits.len()); assert_eq!("my\nexample\narticle\n", edit_res.article.text); @@ -752,14 +759,15 @@ async fn test_article_approval_required() -> MyResult<()> { }; data.alpha.login(form).await?; - let list_approval_required = data.alpha.list_articles_approval_required().await?; - assert_eq!(1, list_approval_required.len()); - assert_eq!(create_res.article.id, list_approval_required[0].id); + assert_eq!(1, data.alpha.notifications_count().await?); + let notifications = data.alpha.notifications_list().await?; + assert_eq!(1, notifications.len()); + let Notification::ArticleApprovalRequired(notif) = ¬ifications[0] else { + panic!() + }; + assert_eq!(create_res.article.id, notif.id); - let approve = data - .alpha - .approve_article(list_approval_required[0].id) - .await?; + let approve = data.alpha.approve_article(notif.id).await?; assert_eq!(create_res.article.id, approve.id); assert!(approve.approved);