1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2025-02-04 02:41:36 +00:00

Split up api files

This commit is contained in:
Felix Ableitner 2025-01-23 22:20:24 +01:00
parent cdcc992b75
commit ae44c169ba
8 changed files with 521 additions and 424 deletions

View file

@ -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<ApiClient> = 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<String>) -> 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<DbArticleView> {
self.get("/api/v1/article", Some(data)).await
}
pub async fn list_articles(&self, data: ListArticlesParams) -> Option<Vec<DbArticle>> {
Some(self.get("/api/v1/article/list", Some(data)).await.unwrap())
}
pub async fn register(
&self,
params: RegisterUserParams,
) -> Result<LocalUserView, ServerFnError> {
self.post("/api/v1/account/register", Some(&params)).await
}
pub async fn login(&self, params: LoginUserParams) -> Result<LocalUserView, ServerFnError> {
self.post("/api/v1/account/login", Some(&params)).await
}
pub async fn create_article(
&self,
data: &CreateArticleParams,
) -> Result<DbArticleView, ServerFnError> {
self.post("/api/v1/article", Some(&data)).await
}
pub async fn edit_article_with_conflict(
&self,
params: &EditArticleParams,
) -> Result<Option<ApiConflict>, ServerFnError> {
self.patch("/api/v1/article", Some(&params)).await
}
#[cfg(debug_assertions)]
pub async fn edit_article(&self, params: &EditArticleParams) -> Option<DbArticleView> {
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<DbCommentView, ServerFnError> {
self.post("/api/v1/comment", Some(&params)).await
}
pub async fn edit_comment(
&self,
params: &EditCommentParams,
) -> Result<DbCommentView, ServerFnError> {
self.patch("/api/v1/comment", Some(&params)).await
}
pub async fn notifications_list(&self) -> Option<Vec<Notification>> {
self.get("/api/v1/user/notifications/list", None::<()>)
.await
}
pub async fn notifications_count(&self) -> Option<usize> {
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(&params)).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<Vec<DbArticle>, ServerFnError> {
self.send(Method::GET, "/api/v1/search", Some(params)).await
}
pub async fn get_local_instance(&self) -> Option<InstanceView> {
self.get("/api/v1/instance", None::<i32>).await
}
pub async fn get_instance(&self, params: &GetInstanceParams) -> Option<InstanceView> {
self.get("/api/v1/instance", Some(&params)).await
}
pub async fn list_instances(&self) -> Option<Vec<DbInstance>> {
self.get("/api/v1/instance/list", None::<i32>).await
}
pub async fn update_local_instance(
&self,
params: &UpdateInstanceParams,
) -> Result<DbInstance, ServerFnError> {
self.patch("/api/v1/instance", Some(params)).await
}
pub async fn follow_instance_with_resolve(&self, follow_instance: &str) -> Option<DbInstance> {
// 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<SuccessResponse> {
result_to_option(self.post("/api/v1/instance/follow", Some(params)).await)
}
pub async fn site(&self) -> Option<SiteView> {
self.get("/api/v1/site", None::<()>).await
}
pub async fn logout(&self) -> Option<SuccessResponse> {
result_to_option(self.post("/api/v1/account/logout", None::<()>).await)
}
pub async fn fork_article(
&self,
params: &ForkArticleParams,
) -> Result<DbArticleView, ServerFnError> {
self.post("/api/v1/article/fork", Some(params)).await
}
pub async fn protect_article(
&self,
params: &ProtectArticleParams,
) -> Result<DbArticle, ServerFnError> {
self.post("/api/v1/article/protect", Some(params)).await
}
pub async fn resolve_article(&self, id: Url) -> Result<DbArticleView, ServerFnError> {
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<DbInstance, ServerFnError> {
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<DbPerson> {
self.get("/api/v1/user", Some(data)).await
}
pub async fn update_user_profile(
&self,
data: UpdateUserParams,
) -> Result<SuccessResponse, ServerFnError> {
self.post("/api/v1/account/update", Some(data)).await
}
pub async fn get_article_edits(&self, article_id: ArticleId) -> Option<Vec<EditView>> {
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<Vec<EditView>> {
let data = GetEditList {
person_id: Some(person_id),
..Default::default()
};
self.get("/api/v1/edit/list", Some(data)).await
}
async fn get<T, R>(&self, endpoint: &str, query: Option<R>) -> Option<T>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
result_to_option(self.send(Method::GET, endpoint, query).await)
}
async fn post<T, R>(&self, endpoint: &str, query: Option<R>) -> Result<T, ServerFnError>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
self.send(Method::POST, endpoint, query).await
}
async fn patch<T, R>(&self, endpoint: &str, query: Option<R>) -> Result<T, ServerFnError>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
self.send(Method::PATCH, endpoint, query).await
}
#[cfg(feature = "ssr")]
async fn send<P, T>(
&self,
method: Method,
path: &str,
params: Option<P>,
) -> Result<T, ServerFnError>
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(&params)
} else {
req.form(&params)
};
let auth = use_context::<Auth>();
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<P>,
) -> impl std::future::Future<Output = Result<T, ServerFnError>> + 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(&params).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<T>(status: u16, text: String, url: &str) -> Result<T, ServerFnError>
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::<NoCustomError>::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<T>(val: Result<T, ServerFnError>) -> Option<T> {
match val {
Ok(v) => Some(v),
Err(e) => {
error!("API error: {e}");
None
}
}
}

112
src/frontend/api/article.rs Normal file
View file

@ -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<DbArticleView, ServerFnError> {
self.post("/api/v1/article", Some(&data)).await
}
pub async fn get_article(&self, data: GetArticleParams) -> Option<DbArticleView> {
self.get("/api/v1/article", Some(data)).await
}
pub async fn list_articles(&self, data: ListArticlesParams) -> Option<Vec<DbArticle>> {
Some(self.get("/api/v1/article/list", Some(data)).await.unwrap())
}
pub async fn edit_article(
&self,
params: &EditArticleParams,
) -> Result<Option<ApiConflict>, ServerFnError> {
self.patch("/api/v1/article", Some(&params)).await
}
pub async fn fork_article(
&self,
params: &ForkArticleParams,
) -> Result<DbArticleView, ServerFnError> {
self.post("/api/v1/article/fork", Some(params)).await
}
pub async fn protect_article(
&self,
params: &ProtectArticleParams,
) -> Result<DbArticle, ServerFnError> {
self.post("/api/v1/article/protect", Some(params)).await
}
pub async fn resolve_article(&self, id: Url) -> Result<DbArticleView, ServerFnError> {
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<Vec<EditView>> {
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(&params)).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<DbArticleView> {
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
}
}

View file

@ -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<DbCommentView, ServerFnError> {
self.post("/api/v1/comment", Some(&params)).await
}
pub async fn edit_comment(
&self,
params: &EditCommentParams,
) -> Result<DbCommentView, ServerFnError> {
self.patch("/api/v1/comment", Some(&params)).await
}
}

View file

@ -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<InstanceView> {
self.get("/api/v1/instance", None::<i32>).await
}
pub async fn get_instance(&self, params: &GetInstanceParams) -> Option<InstanceView> {
self.get("/api/v1/instance", Some(&params)).await
}
pub async fn list_instances(&self) -> Option<Vec<DbInstance>> {
self.get("/api/v1/instance/list", None::<i32>).await
}
pub async fn update_local_instance(
&self,
params: &UpdateInstanceParams,
) -> Result<DbInstance, ServerFnError> {
self.patch("/api/v1/instance", Some(params)).await
}
pub async fn notifications_list(&self) -> Option<Vec<Notification>> {
self.get("/api/v1/user/notifications/list", None::<()>)
.await
}
pub async fn notifications_count(&self) -> Option<usize> {
self.get("/api/v1/user/notifications/count", None::<()>)
.await
}
pub async fn search(
&self,
params: &SearchArticleParams,
) -> Result<Vec<DbArticle>, ServerFnError> {
self.send(Method::GET, "/api/v1/search", Some(params)).await
}
pub async fn resolve_instance(&self, id: Url) -> Result<DbInstance, ServerFnError> {
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<SuccessResponse> {
result_to_option(self.post("/api/v1/instance/follow", Some(params)).await)
}
pub async fn site(&self) -> Option<SiteView> {
self.get("/api/v1/site", None::<()>).await
}
#[cfg(debug_assertions)]
pub async fn follow_instance_with_resolve(&self, follow_instance: &str) -> Option<DbInstance> {
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)
}
}

198
src/frontend/api/mod.rs Normal file
View file

@ -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<ApiClient> = 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<String>) -> 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<T, R>(&self, endpoint: &str, query: Option<R>) -> Option<T>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
result_to_option(self.send(Method::GET, endpoint, query).await)
}
async fn post<T, R>(&self, endpoint: &str, query: Option<R>) -> Result<T, ServerFnError>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
self.send(Method::POST, endpoint, query).await
}
async fn patch<T, R>(&self, endpoint: &str, query: Option<R>) -> Result<T, ServerFnError>
where
T: for<'de> Deserialize<'de>,
R: Serialize + Debug,
{
self.send(Method::PATCH, endpoint, query).await
}
#[cfg(feature = "ssr")]
async fn send<P, T>(
&self,
method: Method,
path: &str,
params: Option<P>,
) -> Result<T, ServerFnError>
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(&params)
} else {
req.form(&params)
};
let auth = use_context::<Auth>();
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<P>,
) -> impl std::future::Future<Output = Result<T, ServerFnError>> + 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(&params).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<T>(status: u16, text: String, url: &str) -> Result<T, ServerFnError>
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::<NoCustomError>::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<T>(val: Result<T, ServerFnError>) -> Option<T> {
match val {
Ok(v) => Some(v),
Err(e) => {
error!("API error: {e}");
None
}
}
}

51
src/frontend/api/user.rs Normal file
View file

@ -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<LocalUserView, ServerFnError> {
self.post("/api/v1/account/register", Some(&params)).await
}
pub async fn login(&self, params: LoginUserParams) -> Result<LocalUserView, ServerFnError> {
self.post("/api/v1/account/login", Some(&params)).await
}
pub async fn logout(&self) -> Option<SuccessResponse> {
result_to_option(self.post("/api/v1/account/logout", None::<()>).await)
}
pub async fn get_user(&self, data: GetUserParams) -> Option<DbPerson> {
self.get("/api/v1/user", Some(data)).await
}
pub async fn update_user_profile(
&self,
data: UpdateUserParams,
) -> Result<SuccessResponse, ServerFnError> {
self.post("/api/v1/account/update", Some(data)).await
}
pub async fn get_person_edits(&self, person_id: PersonId) -> Option<Vec<EditView>> {
let data = GetEditList {
person_id: Some(person_id),
..Default::default()
};
self.get("/api/v1/edit/list", Some(data)).await
}
}

View file

@ -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(&params).await;
let res = CLIENT.edit_article(&params).await;
set_wait_for_response.update(|w| *w = false);
match res {
Ok(Some(conflict)) => {

View file

@ -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)