diff --git a/src/frontend/api.rs b/src/frontend/api.rs deleted file mode 100644 index 32af1d4..0000000 --- a/src/frontend/api.rs +++ /dev/null @@ -1,406 +0,0 @@ -use crate::common::{ - article::*, - instance::*, - newtypes::{ArticleId, ConflictId, PersonId}, - user::*, - utils::http_protocol_str, - *, -}; -use comment::{CreateCommentParams, DbCommentView, EditCommentParams}; -use http::{Method, StatusCode}; -use leptos::{prelude::ServerFnError, server_fn::error::NoCustomError}; -use log::{error, info}; -use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, sync::LazyLock}; -use url::Url; - -pub static CLIENT: LazyLock = LazyLock::new(|| { - #[cfg(feature = "ssr")] - { - ApiClient::new(reqwest::Client::new(), None) - } - #[cfg(not(feature = "ssr"))] - { - ApiClient::new() - } -}); - -#[derive(Clone, Debug)] -pub struct ApiClient { - #[cfg(feature = "ssr")] - client: reqwest::Client, - pub hostname: String, - ssl: bool, -} - -impl ApiClient { - #[cfg(feature = "ssr")] - pub fn new(client: reqwest::Client, hostname_: Option) -> Self { - use leptos::config::get_config_from_str; - let leptos_options = get_config_from_str(include_str!("../../Cargo.toml")).unwrap(); - let mut hostname = leptos_options.site_addr.to_string(); - // required for tests - if let Some(hostname_) = hostname_ { - hostname = hostname_; - } - Self { - client, - hostname, - ssl: false, - } - } - #[cfg(not(feature = "ssr"))] - pub fn new() -> Self { - use leptos_use::use_document; - let hostname = use_document().location().unwrap().host().unwrap(); - let ssl = !cfg!(debug_assertions); - Self { hostname, ssl } - } - - pub async fn get_article(&self, data: GetArticleParams) -> Option { - self.get("/api/v1/article", Some(data)).await - } - - pub async fn list_articles(&self, data: ListArticlesParams) -> Option> { - Some(self.get("/api/v1/article/list", Some(data)).await.unwrap()) - } - - pub async fn register( - &self, - params: RegisterUserParams, - ) -> Result { - self.post("/api/v1/account/register", Some(¶ms)).await - } - - pub async fn login(&self, params: LoginUserParams) -> Result { - self.post("/api/v1/account/login", Some(¶ms)).await - } - - pub async fn create_article( - &self, - data: &CreateArticleParams, - ) -> Result { - self.post("/api/v1/article", Some(&data)).await - } - - pub async fn edit_article_with_conflict( - &self, - params: &EditArticleParams, - ) -> Result, ServerFnError> { - self.patch("/api/v1/article", Some(¶ms)).await - } - - #[cfg(debug_assertions)] - pub async fn edit_article(&self, params: &EditArticleParams) -> Option { - let edit_res = self - .edit_article_with_conflict(params) - .await - .map_err(|e| error!("edit failed {e}")) - .ok()?; - assert_eq!(None, edit_res); - - self.get_article(GetArticleParams { - title: None, - domain: None, - id: Some(params.article_id), - }) - .await - } - - pub async fn create_comment( - &self, - params: &CreateCommentParams, - ) -> Result { - self.post("/api/v1/comment", Some(¶ms)).await - } - - pub async fn edit_comment( - &self, - params: &EditCommentParams, - ) -> Result { - self.patch("/api/v1/comment", Some(¶ms)).await - } - - pub async fn notifications_list(&self) -> Option> { - self.get("/api/v1/user/notifications/list", None::<()>) - .await - } - - pub async fn notifications_count(&self) -> Option { - self.get("/api/v1/user/notifications/count", None::<()>) - .await - } - - pub async fn approve_article(&self, article_id: ArticleId, approve: bool) -> Option<()> { - let params = ApproveArticleParams { - article_id, - approve, - }; - result_to_option(self.post("/api/v1/article/approve", Some(¶ms)).await) - } - - pub async fn delete_conflict(&self, conflict_id: ConflictId) -> Option<()> { - let params = DeleteConflictParams { conflict_id }; - result_to_option( - self.send(Method::DELETE, "/api/v1/conflict", Some(params)) - .await, - ) - } - - pub async fn search( - &self, - params: &SearchArticleParams, - ) -> Result, ServerFnError> { - self.send(Method::GET, "/api/v1/search", Some(params)).await - } - - pub async fn get_local_instance(&self) -> Option { - self.get("/api/v1/instance", None::).await - } - - pub async fn get_instance(&self, params: &GetInstanceParams) -> Option { - self.get("/api/v1/instance", Some(¶ms)).await - } - - pub async fn list_instances(&self) -> Option> { - self.get("/api/v1/instance/list", None::).await - } - - pub async fn update_local_instance( - &self, - params: &UpdateInstanceParams, - ) -> Result { - self.patch("/api/v1/instance", Some(params)).await - } - - pub async fn follow_instance_with_resolve(&self, follow_instance: &str) -> Option { - // fetch beta instance on alpha - let params = ResolveObjectParams { - id: Url::parse(&format!("{}://{}", http_protocol_str(), follow_instance)) - .map_err(|e| error!("invalid url {e}")) - .ok()?, - }; - let instance_resolved: DbInstance = - self.get("/api/v1/instance/resolve", Some(params)).await?; - - // send follow - let params = FollowInstanceParams { - id: instance_resolved.id, - }; - self.follow_instance(params).await?; - Some(instance_resolved) - } - - pub async fn follow_instance(&self, params: FollowInstanceParams) -> Option { - result_to_option(self.post("/api/v1/instance/follow", Some(params)).await) - } - - pub async fn site(&self) -> Option { - self.get("/api/v1/site", None::<()>).await - } - - pub async fn logout(&self) -> Option { - result_to_option(self.post("/api/v1/account/logout", None::<()>).await) - } - - pub async fn fork_article( - &self, - params: &ForkArticleParams, - ) -> Result { - self.post("/api/v1/article/fork", Some(params)).await - } - - pub async fn protect_article( - &self, - params: &ProtectArticleParams, - ) -> Result { - self.post("/api/v1/article/protect", Some(params)).await - } - - pub async fn resolve_article(&self, id: Url) -> Result { - let resolve_object = ResolveObjectParams { id }; - self.send(Method::GET, "/api/v1/article/resolve", Some(resolve_object)) - .await - } - - pub async fn resolve_instance(&self, id: Url) -> Result { - let resolve_object = ResolveObjectParams { id }; - self.send( - Method::GET, - "/api/v1/instance/resolve", - Some(resolve_object), - ) - .await - } - - pub async fn get_user(&self, data: GetUserParams) -> Option { - self.get("/api/v1/user", Some(data)).await - } - - pub async fn update_user_profile( - &self, - data: UpdateUserParams, - ) -> Result { - self.post("/api/v1/account/update", Some(data)).await - } - - pub async fn get_article_edits(&self, article_id: ArticleId) -> Option> { - let data = GetEditList { - article_id: Some(article_id), - ..Default::default() - }; - self.get("/api/v1/edit/list", Some(data)).await - } - - pub async fn get_person_edits(&self, person_id: PersonId) -> Option> { - let data = GetEditList { - person_id: Some(person_id), - ..Default::default() - }; - self.get("/api/v1/edit/list", Some(data)).await - } - - async fn get(&self, endpoint: &str, query: Option) -> Option - where - T: for<'de> Deserialize<'de>, - R: Serialize + Debug, - { - result_to_option(self.send(Method::GET, endpoint, query).await) - } - - async fn post(&self, endpoint: &str, query: Option) -> Result - where - T: for<'de> Deserialize<'de>, - R: Serialize + Debug, - { - self.send(Method::POST, endpoint, query).await - } - - async fn patch(&self, endpoint: &str, query: Option) -> Result - where - T: for<'de> Deserialize<'de>, - R: Serialize + Debug, - { - self.send(Method::PATCH, endpoint, query).await - } - - #[cfg(feature = "ssr")] - async fn send( - &self, - method: Method, - path: &str, - params: Option

, - ) -> Result - where - P: Serialize + Debug, - T: for<'de> Deserialize<'de>, - { - use crate::common::{Auth, AUTH_COOKIE}; - use leptos::prelude::use_context; - use reqwest::header::HeaderName; - let mut req = self - .client - .request(method.clone(), self.request_endpoint(path)); - req = if method == Method::GET { - req.query(¶ms) - } else { - req.form(¶ms) - }; - let auth = use_context::(); - if let Some(Auth(Some(auth))) = auth { - req = req.header(HeaderName::from_static(AUTH_COOKIE), auth); - } - let res = req.send().await?; - let status = res.status(); - let url = res.url().to_string(); - let text = res.text().await?.to_string(); - Self::response(status.into(), text, &url) - } - - #[cfg(not(feature = "ssr"))] - fn send<'a, P, T>( - &'a self, - method: Method, - path: &'a str, - params: Option

, - ) -> impl std::future::Future> + Send + 'a - where - P: Serialize + Debug + 'a, - T: for<'de> Deserialize<'de>, - { - use gloo_net::http::*; - use leptos::prelude::on_cleanup; - use send_wrapper::SendWrapper; - use web_sys::RequestCredentials; - - SendWrapper::new(async move { - let abort_controller = SendWrapper::new(web_sys::AbortController::new().ok()); - let abort_signal = abort_controller.as_ref().map(|a| a.signal()); - - // abort in-flight requests if, e.g., we've navigated away from this page - on_cleanup(move || { - if let Some(abort_controller) = abort_controller.take() { - abort_controller.abort() - } - }); - - let path_with_endpoint = self.request_endpoint(path); - let params_encoded = serde_urlencoded::to_string(¶ms).unwrap(); - let path = if method == Method::GET { - // Cannot pass the form data directly but need to convert it manually - // https://github.com/rustwasm/gloo/issues/378 - format!("{path_with_endpoint}?{params_encoded}") - } else { - path_with_endpoint - }; - - let builder = RequestBuilder::new(&path) - .method(method.clone()) - .abort_signal(abort_signal.as_ref()) - .credentials(RequestCredentials::Include); - let req = if method != Method::GET { - builder - .header("content-type", "application/x-www-form-urlencoded") - .body(params_encoded) - } else { - builder.build() - } - .unwrap(); - let res = req.send().await?; - let status = res.status(); - let text = res.text().await?; - Self::response(status, text, &res.url()) - }) - } - - fn response(status: u16, text: String, url: &str) -> Result - where - T: for<'de> Deserialize<'de>, - { - let json = serde_json::from_str(&text).map_err(|e| { - info!("Failed to deserialize api response: {e} from {text} on {url}"); - ServerFnError::::Deserialization(text.clone()) - })?; - if status == StatusCode::OK { - Ok(json) - } else { - info!("API error: {text} on {url} status {status}"); - Err(ServerFnError::Response(text)) - } - } - - fn request_endpoint(&self, path: &str) -> String { - let protocol = if self.ssl { "https" } else { "http" }; - format!("{protocol}://{}{path}", &self.hostname) - } -} - -fn result_to_option(val: Result) -> Option { - match val { - Ok(v) => Some(v), - Err(e) => { - error!("API error: {e}"); - None - } - } -} diff --git a/src/frontend/api/article.rs b/src/frontend/api/article.rs new file mode 100644 index 0000000..3936006 --- /dev/null +++ b/src/frontend/api/article.rs @@ -0,0 +1,112 @@ +use super::{result_to_option, ApiClient}; +use crate::common::{ + article::{ + ApiConflict, + ApproveArticleParams, + CreateArticleParams, + DbArticle, + DbArticleView, + DeleteConflictParams, + EditArticleParams, + EditView, + ForkArticleParams, + GetArticleParams, + GetEditList, + ListArticlesParams, + ProtectArticleParams, + }, + newtypes::{ArticleId, ConflictId}, + ResolveObjectParams, +}; +use http::Method; +use leptos::prelude::ServerFnError; +use log::error; +use url::Url; + +impl ApiClient { + pub async fn create_article( + &self, + data: &CreateArticleParams, + ) -> Result { + self.post("/api/v1/article", Some(&data)).await + } + + pub async fn get_article(&self, data: GetArticleParams) -> Option { + self.get("/api/v1/article", Some(data)).await + } + + pub async fn list_articles(&self, data: ListArticlesParams) -> Option> { + Some(self.get("/api/v1/article/list", Some(data)).await.unwrap()) + } + + pub async fn edit_article( + &self, + params: &EditArticleParams, + ) -> Result, ServerFnError> { + self.patch("/api/v1/article", Some(¶ms)).await + } + + pub async fn fork_article( + &self, + params: &ForkArticleParams, + ) -> Result { + self.post("/api/v1/article/fork", Some(params)).await + } + + pub async fn protect_article( + &self, + params: &ProtectArticleParams, + ) -> Result { + self.post("/api/v1/article/protect", Some(params)).await + } + + pub async fn resolve_article(&self, id: Url) -> Result { + let resolve_object = ResolveObjectParams { id }; + self.send(Method::GET, "/api/v1/article/resolve", Some(resolve_object)) + .await + } + + pub async fn get_article_edits(&self, article_id: ArticleId) -> Option> { + let data = GetEditList { + article_id: Some(article_id), + ..Default::default() + }; + self.get("/api/v1/edit/list", Some(data)).await + } + + pub async fn approve_article(&self, article_id: ArticleId, approve: bool) -> Option<()> { + let params = ApproveArticleParams { + article_id, + approve, + }; + result_to_option(self.post("/api/v1/article/approve", Some(¶ms)).await) + } + + pub async fn delete_conflict(&self, conflict_id: ConflictId) -> Option<()> { + let params = DeleteConflictParams { conflict_id }; + result_to_option( + self.send(Method::DELETE, "/api/v1/conflict", Some(params)) + .await, + ) + } + + #[cfg(debug_assertions)] + pub async fn edit_article_without_conflict( + &self, + params: &EditArticleParams, + ) -> Option { + let edit_res = self + .edit_article(params) + .await + .map_err(|e| error!("edit failed {e}")) + .ok()?; + assert_eq!(None, edit_res); + + self.get_article(GetArticleParams { + title: None, + domain: None, + id: Some(params.article_id), + }) + .await + } +} diff --git a/src/frontend/api/comment.rs b/src/frontend/api/comment.rs new file mode 100644 index 0000000..fe61599 --- /dev/null +++ b/src/frontend/api/comment.rs @@ -0,0 +1,19 @@ +use super::ApiClient; +use crate::common::comment::{CreateCommentParams, DbCommentView, EditCommentParams}; +use leptos::prelude::ServerFnError; + +impl ApiClient { + pub async fn create_comment( + &self, + params: &CreateCommentParams, + ) -> Result { + self.post("/api/v1/comment", Some(¶ms)).await + } + + pub async fn edit_comment( + &self, + params: &EditCommentParams, + ) -> Result { + self.patch("/api/v1/comment", Some(¶ms)).await + } +} diff --git a/src/frontend/api/instance.rs b/src/frontend/api/instance.rs new file mode 100644 index 0000000..4894d77 --- /dev/null +++ b/src/frontend/api/instance.rs @@ -0,0 +1,94 @@ +use super::{result_to_option, ApiClient}; +use crate::common::{ + article::{DbArticle, SearchArticleParams}, + instance::{ + DbInstance, + FollowInstanceParams, + GetInstanceParams, + InstanceView, + SiteView, + UpdateInstanceParams, + }, + Notification, + ResolveObjectParams, + SuccessResponse, +}; +use http::Method; +use leptos::prelude::ServerFnError; +use url::Url; + +impl ApiClient { + pub async fn get_local_instance(&self) -> Option { + self.get("/api/v1/instance", None::).await + } + + pub async fn get_instance(&self, params: &GetInstanceParams) -> Option { + self.get("/api/v1/instance", Some(¶ms)).await + } + + pub async fn list_instances(&self) -> Option> { + self.get("/api/v1/instance/list", None::).await + } + + pub async fn update_local_instance( + &self, + params: &UpdateInstanceParams, + ) -> Result { + self.patch("/api/v1/instance", Some(params)).await + } + + pub async fn notifications_list(&self) -> Option> { + self.get("/api/v1/user/notifications/list", None::<()>) + .await + } + + pub async fn notifications_count(&self) -> Option { + self.get("/api/v1/user/notifications/count", None::<()>) + .await + } + pub async fn search( + &self, + params: &SearchArticleParams, + ) -> Result, ServerFnError> { + self.send(Method::GET, "/api/v1/search", Some(params)).await + } + + pub async fn resolve_instance(&self, id: Url) -> Result { + let resolve_object = ResolveObjectParams { id }; + self.send( + Method::GET, + "/api/v1/instance/resolve", + Some(resolve_object), + ) + .await + } + + pub async fn follow_instance(&self, params: FollowInstanceParams) -> Option { + result_to_option(self.post("/api/v1/instance/follow", Some(params)).await) + } + + pub async fn site(&self) -> Option { + self.get("/api/v1/site", None::<()>).await + } + + #[cfg(debug_assertions)] + pub async fn follow_instance_with_resolve(&self, follow_instance: &str) -> Option { + use crate::common::{utils::http_protocol_str, ResolveObjectParams}; + use log::error; + use url::Url; + let params = ResolveObjectParams { + id: Url::parse(&format!("{}://{}", http_protocol_str(), follow_instance)) + .map_err(|e| error!("invalid url {e}")) + .ok()?, + }; + let instance_resolved: DbInstance = + self.get("/api/v1/instance/resolve", Some(params)).await?; + + // send follow + let params = FollowInstanceParams { + id: instance_resolved.id, + }; + self.follow_instance(params).await?; + Some(instance_resolved) + } +} diff --git a/src/frontend/api/mod.rs b/src/frontend/api/mod.rs new file mode 100644 index 0000000..3b1ce5b --- /dev/null +++ b/src/frontend/api/mod.rs @@ -0,0 +1,198 @@ +use http::{Method, StatusCode}; +use leptos::{prelude::ServerFnError, server_fn::error::NoCustomError}; +use log::{error, info}; +use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, sync::LazyLock}; + +pub mod article; +pub mod comment; +pub mod instance; +pub mod user; + +pub static CLIENT: LazyLock = LazyLock::new(|| { + #[cfg(feature = "ssr")] + { + ApiClient::new(reqwest::Client::new(), None) + } + #[cfg(not(feature = "ssr"))] + { + ApiClient::new() + } +}); + +#[derive(Clone, Debug)] +pub struct ApiClient { + #[cfg(feature = "ssr")] + client: reqwest::Client, + pub hostname: String, + ssl: bool, +} + +impl ApiClient { + #[cfg(feature = "ssr")] + pub fn new(client: reqwest::Client, hostname_: Option) -> Self { + use leptos::config::get_config_from_str; + let leptos_options = get_config_from_str(include_str!("../../../Cargo.toml")).unwrap(); + let mut hostname = leptos_options.site_addr.to_string(); + // required for tests + if let Some(hostname_) = hostname_ { + hostname = hostname_; + } + Self { + client, + hostname, + ssl: false, + } + } + #[cfg(not(feature = "ssr"))] + pub fn new() -> Self { + use leptos_use::use_document; + let hostname = use_document().location().unwrap().host().unwrap(); + let ssl = !cfg!(debug_assertions); + Self { hostname, ssl } + } + + async fn get(&self, endpoint: &str, query: Option) -> Option + where + T: for<'de> Deserialize<'de>, + R: Serialize + Debug, + { + result_to_option(self.send(Method::GET, endpoint, query).await) + } + + async fn post(&self, endpoint: &str, query: Option) -> Result + where + T: for<'de> Deserialize<'de>, + R: Serialize + Debug, + { + self.send(Method::POST, endpoint, query).await + } + + async fn patch(&self, endpoint: &str, query: Option) -> Result + where + T: for<'de> Deserialize<'de>, + R: Serialize + Debug, + { + self.send(Method::PATCH, endpoint, query).await + } + + #[cfg(feature = "ssr")] + async fn send( + &self, + method: Method, + path: &str, + params: Option

, + ) -> Result + where + P: Serialize + Debug, + T: for<'de> Deserialize<'de>, + { + use crate::common::{Auth, AUTH_COOKIE}; + use leptos::prelude::use_context; + use reqwest::header::HeaderName; + let mut req = self + .client + .request(method.clone(), self.request_endpoint(path)); + req = if method == Method::GET { + req.query(¶ms) + } else { + req.form(¶ms) + }; + let auth = use_context::(); + if let Some(Auth(Some(auth))) = auth { + req = req.header(HeaderName::from_static(AUTH_COOKIE), auth); + } + let res = req.send().await?; + let status = res.status(); + let url = res.url().to_string(); + let text = res.text().await?.to_string(); + Self::response(status.into(), text, &url) + } + + #[cfg(not(feature = "ssr"))] + fn send<'a, P, T>( + &'a self, + method: Method, + path: &'a str, + params: Option

, + ) -> impl std::future::Future> + Send + 'a + where + P: Serialize + Debug + 'a, + T: for<'de> Deserialize<'de>, + { + use gloo_net::http::*; + use leptos::prelude::on_cleanup; + use send_wrapper::SendWrapper; + use web_sys::RequestCredentials; + + SendWrapper::new(async move { + let abort_controller = SendWrapper::new(web_sys::AbortController::new().ok()); + let abort_signal = abort_controller.as_ref().map(|a| a.signal()); + + // abort in-flight requests if, e.g., we've navigated away from this page + on_cleanup(move || { + if let Some(abort_controller) = abort_controller.take() { + abort_controller.abort() + } + }); + + let path_with_endpoint = self.request_endpoint(path); + let params_encoded = serde_urlencoded::to_string(¶ms).unwrap(); + let path = if method == Method::GET { + // Cannot pass the form data directly but need to convert it manually + // https://github.com/rustwasm/gloo/issues/378 + format!("{path_with_endpoint}?{params_encoded}") + } else { + path_with_endpoint + }; + + let builder = RequestBuilder::new(&path) + .method(method.clone()) + .abort_signal(abort_signal.as_ref()) + .credentials(RequestCredentials::Include); + let req = if method != Method::GET { + builder + .header("content-type", "application/x-www-form-urlencoded") + .body(params_encoded) + } else { + builder.build() + } + .unwrap(); + let res = req.send().await?; + let status = res.status(); + let text = res.text().await?; + Self::response(status, text, &res.url()) + }) + } + + fn response(status: u16, text: String, url: &str) -> Result + where + T: for<'de> Deserialize<'de>, + { + let json = serde_json::from_str(&text).map_err(|e| { + info!("Failed to deserialize api response: {e} from {text} on {url}"); + ServerFnError::::Deserialization(text.clone()) + })?; + if status == StatusCode::OK { + Ok(json) + } else { + info!("API error: {text} on {url} status {status}"); + Err(ServerFnError::Response(text)) + } + } + + fn request_endpoint(&self, path: &str) -> String { + let protocol = if self.ssl { "https" } else { "http" }; + format!("{protocol}://{}{path}", &self.hostname) + } +} + +fn result_to_option(val: Result) -> Option { + match val { + Ok(v) => Some(v), + Err(e) => { + error!("API error: {e}"); + None + } + } +} diff --git a/src/frontend/api/user.rs b/src/frontend/api/user.rs new file mode 100644 index 0000000..9ba4ec7 --- /dev/null +++ b/src/frontend/api/user.rs @@ -0,0 +1,51 @@ +use super::{result_to_option, ApiClient}; +use crate::common::{ + article::{EditView, GetEditList}, + newtypes::PersonId, + user::{ + DbPerson, + GetUserParams, + LocalUserView, + LoginUserParams, + RegisterUserParams, + UpdateUserParams, + }, + SuccessResponse, +}; +use leptos::prelude::ServerFnError; + +impl ApiClient { + pub async fn register( + &self, + params: RegisterUserParams, + ) -> Result { + self.post("/api/v1/account/register", Some(¶ms)).await + } + + pub async fn login(&self, params: LoginUserParams) -> Result { + self.post("/api/v1/account/login", Some(¶ms)).await + } + + pub async fn logout(&self) -> Option { + result_to_option(self.post("/api/v1/account/logout", None::<()>).await) + } + + pub async fn get_user(&self, data: GetUserParams) -> Option { + self.get("/api/v1/user", Some(data)).await + } + + pub async fn update_user_profile( + &self, + data: UpdateUserParams, + ) -> Result { + self.post("/api/v1/account/update", Some(data)).await + } + + pub async fn get_person_edits(&self, person_id: PersonId) -> Option> { + let data = GetEditList { + person_id: Some(person_id), + ..Default::default() + }; + self.get("/api/v1/edit/list", Some(data)).await + } +} diff --git a/src/frontend/pages/article/edit.rs b/src/frontend/pages/article/edit.rs index ef08fec..cf2b068 100644 --- a/src/frontend/pages/article/edit.rs +++ b/src/frontend/pages/article/edit.rs @@ -95,7 +95,7 @@ pub fn EditArticle() -> impl IntoView { resolve_conflict_id, }; set_wait_for_response.update(|w| *w = true); - let res = CLIENT.edit_article_with_conflict(¶ms).await; + let res = CLIENT.edit_article(¶ms).await; set_wait_for_response.update(|w| *w = false); match res { Ok(Some(conflict)) => { diff --git a/tests/test.rs b/tests/test.rs index c4bd231..e5bcf88 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -64,7 +64,10 @@ async fn test_create_read_and_edit_local_article() -> Result<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); assert_eq!(edit_params.new_text, edit_res.article.text); let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(2, edits.len()); @@ -164,7 +167,10 @@ async fn test_synchronize_articles() -> Result<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); // fetch alpha instance on beta, articles are also fetched automatically let instance = beta @@ -251,7 +257,10 @@ async fn test_edit_local_article() -> Result<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = beta.edit_article(&edit_params).await.unwrap(); + let edit_res = beta + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let edits = beta.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(edit_res.article.text, edit_params.new_text); assert_eq!(edits.len(), 2); @@ -328,7 +337,10 @@ async fn test_edit_remote_article() -> Result<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); assert_eq!(edit_params.new_text, edit_res.article.text); let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(2, edits.len()); @@ -377,7 +389,10 @@ async fn test_local_edit_conflict() -> Result<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(edit_res.article.text, edit_params.new_text); assert_eq!(2, edits.len()); @@ -390,11 +405,7 @@ async fn test_local_edit_conflict() -> Result<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = alpha - .edit_article_with_conflict(&edit_params) - .await - .unwrap() - .unwrap(); + let edit_res = alpha.edit_article(&edit_params).await.unwrap().unwrap(); assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome example text\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge); let notifications = alpha.notifications_list().await.unwrap(); @@ -411,7 +422,10 @@ async fn test_local_edit_conflict() -> Result<()> { previous_version_id: edit_res.previous_version_id, resolve_conflict_id: Some(edit_res.id), }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); assert_eq!(edit_params.new_text, edit_res.article.text); assert_eq!(0, alpha.notifications_count().await.unwrap()); @@ -463,7 +477,10 @@ async fn test_federated_edit_conflict() -> Result<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let alpha_edits = alpha.get_article_edits(get_res.article.id).await.unwrap(); assert_eq!(edit_res.article.text, edit_params.new_text); assert_eq!(2, alpha_edits.len()); @@ -483,7 +500,10 @@ async fn test_federated_edit_conflict() -> Result<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = gamma.edit_article(&edit_params).await.unwrap(); + let edit_res = gamma + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let gamma_edits = gamma.get_article_edits(edit_res.article.id).await.unwrap(); assert_ne!(edit_params.new_text, edit_res.article.text); assert_eq!(2, gamma_edits.len()); @@ -505,7 +525,10 @@ async fn test_federated_edit_conflict() -> Result<()> { previous_version_id: conflict.previous_version_id.clone(), resolve_conflict_id: Some(conflict.id), }; - let edit_res = gamma.edit_article(&edit_params).await.unwrap(); + let edit_res = gamma + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let gamma_edits = gamma.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(edit_params.new_text, edit_res.article.text); assert_eq!(3, gamma_edits.len()); @@ -552,7 +575,10 @@ async fn test_overlapping_edits_no_conflict() -> Result<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let alpha_edits = alpha.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(edit_res.article.text, edit_params.new_text); assert_eq!(2, alpha_edits.len()); @@ -570,7 +596,10 @@ async fn test_overlapping_edits_no_conflict() -> Result<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = alpha.edit_article(&edit_params).await.unwrap(); + let edit_res = alpha + .edit_article_without_conflict(&edit_params) + .await + .unwrap(); let alpha_edits = alpha.get_article_edits(edit_res.article.id).await.unwrap(); assert_eq!(0, alpha.notifications_count().await.unwrap()); assert_eq!(3, alpha_edits.len()); @@ -748,7 +777,7 @@ async fn test_lock_article() -> Result<()> { previous_version_id: resolve_res.latest_version, resolve_conflict_id: None, }; - let edit_res = gamma.edit_article(&edit_params).await; + let edit_res = gamma.edit_article_without_conflict(&edit_params).await; assert!(edit_res.is_none()); TestData::stop(alpha, beta, gamma)