mirror of
https://github.com/Nutomic/ibis.git
synced 2025-02-04 08:51:35 +00:00
Split up api files
This commit is contained in:
parent
cdcc992b75
commit
ae44c169ba
8 changed files with 521 additions and 424 deletions
|
@ -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(¶ms)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login(&self, params: LoginUserParams) -> Result<LocalUserView, ServerFnError> {
|
|
||||||
self.post("/api/v1/account/login", Some(¶ms)).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(¶ms)).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(¶ms)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn edit_comment(
|
|
||||||
&self,
|
|
||||||
params: &EditCommentParams,
|
|
||||||
) -> Result<DbCommentView, ServerFnError> {
|
|
||||||
self.patch("/api/v1/comment", Some(¶ms)).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(¶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<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(¶ms)).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(¶ms)
|
|
||||||
} else {
|
|
||||||
req.form(¶ms)
|
|
||||||
};
|
|
||||||
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(¶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<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
112
src/frontend/api/article.rs
Normal 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(¶ms)).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(¶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<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
|
||||||
|
}
|
||||||
|
}
|
19
src/frontend/api/comment.rs
Normal file
19
src/frontend/api/comment.rs
Normal 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(¶ms)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn edit_comment(
|
||||||
|
&self,
|
||||||
|
params: &EditCommentParams,
|
||||||
|
) -> Result<DbCommentView, ServerFnError> {
|
||||||
|
self.patch("/api/v1/comment", Some(¶ms)).await
|
||||||
|
}
|
||||||
|
}
|
94
src/frontend/api/instance.rs
Normal file
94
src/frontend/api/instance.rs
Normal 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(¶ms)).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
198
src/frontend/api/mod.rs
Normal 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(¶ms)
|
||||||
|
} else {
|
||||||
|
req.form(¶ms)
|
||||||
|
};
|
||||||
|
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(¶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<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
51
src/frontend/api/user.rs
Normal 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(¶ms)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(&self, params: LoginUserParams) -> Result<LocalUserView, ServerFnError> {
|
||||||
|
self.post("/api/v1/account/login", Some(¶ms)).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
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,7 +95,7 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
resolve_conflict_id,
|
resolve_conflict_id,
|
||||||
};
|
};
|
||||||
set_wait_for_response.update(|w| *w = true);
|
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);
|
set_wait_for_response.update(|w| *w = false);
|
||||||
match res {
|
match res {
|
||||||
Ok(Some(conflict)) => {
|
Ok(Some(conflict)) => {
|
||||||
|
|
|
@ -64,7 +64,10 @@ async fn test_create_read_and_edit_local_article() -> Result<()> {
|
||||||
previous_version_id: get_res.latest_version,
|
previous_version_id: get_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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);
|
assert_eq!(edit_params.new_text, edit_res.article.text);
|
||||||
let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_eq!(2, edits.len());
|
assert_eq!(2, edits.len());
|
||||||
|
@ -164,7 +167,10 @@ async fn test_synchronize_articles() -> Result<()> {
|
||||||
previous_version_id: create_res.latest_version,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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
|
// fetch alpha instance on beta, articles are also fetched automatically
|
||||||
let instance = beta
|
let instance = beta
|
||||||
|
@ -251,7 +257,10 @@ async fn test_edit_local_article() -> Result<()> {
|
||||||
previous_version_id: get_res.latest_version,
|
previous_version_id: get_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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();
|
let edits = beta.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_eq!(edit_res.article.text, edit_params.new_text);
|
assert_eq!(edit_res.article.text, edit_params.new_text);
|
||||||
assert_eq!(edits.len(), 2);
|
assert_eq!(edits.len(), 2);
|
||||||
|
@ -328,7 +337,10 @@ async fn test_edit_remote_article() -> Result<()> {
|
||||||
previous_version_id: get_res.latest_version,
|
previous_version_id: get_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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);
|
assert_eq!(edit_params.new_text, edit_res.article.text);
|
||||||
let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_eq!(2, edits.len());
|
assert_eq!(2, edits.len());
|
||||||
|
@ -377,7 +389,10 @@ async fn test_local_edit_conflict() -> Result<()> {
|
||||||
previous_version_id: create_res.latest_version.clone(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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();
|
let edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_eq!(edit_res.article.text, edit_params.new_text);
|
assert_eq!(edit_res.article.text, edit_params.new_text);
|
||||||
assert_eq!(2, edits.len());
|
assert_eq!(2, edits.len());
|
||||||
|
@ -390,11 +405,7 @@ async fn test_local_edit_conflict() -> Result<()> {
|
||||||
previous_version_id: create_res.latest_version,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
resolve_conflict_id: None,
|
||||||
};
|
};
|
||||||
let edit_res = alpha
|
let edit_res = alpha.edit_article(&edit_params).await.unwrap().unwrap();
|
||||||
.edit_article_with_conflict(&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);
|
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();
|
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,
|
previous_version_id: edit_res.previous_version_id,
|
||||||
resolve_conflict_id: Some(edit_res.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!(edit_params.new_text, edit_res.article.text);
|
||||||
|
|
||||||
assert_eq!(0, alpha.notifications_count().await.unwrap());
|
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(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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();
|
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!(edit_res.article.text, edit_params.new_text);
|
||||||
assert_eq!(2, alpha_edits.len());
|
assert_eq!(2, alpha_edits.len());
|
||||||
|
@ -483,7 +500,10 @@ async fn test_federated_edit_conflict() -> Result<()> {
|
||||||
previous_version_id: create_res.latest_version,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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();
|
let gamma_edits = gamma.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_ne!(edit_params.new_text, edit_res.article.text);
|
assert_ne!(edit_params.new_text, edit_res.article.text);
|
||||||
assert_eq!(2, gamma_edits.len());
|
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(),
|
previous_version_id: conflict.previous_version_id.clone(),
|
||||||
resolve_conflict_id: Some(conflict.id),
|
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();
|
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!(edit_params.new_text, edit_res.article.text);
|
||||||
assert_eq!(3, gamma_edits.len());
|
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(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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();
|
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!(edit_res.article.text, edit_params.new_text);
|
||||||
assert_eq!(2, alpha_edits.len());
|
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,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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();
|
let alpha_edits = alpha.get_article_edits(edit_res.article.id).await.unwrap();
|
||||||
assert_eq!(0, alpha.notifications_count().await.unwrap());
|
assert_eq!(0, alpha.notifications_count().await.unwrap());
|
||||||
assert_eq!(3, alpha_edits.len());
|
assert_eq!(3, alpha_edits.len());
|
||||||
|
@ -748,7 +777,7 @@ async fn test_lock_article() -> Result<()> {
|
||||||
previous_version_id: resolve_res.latest_version,
|
previous_version_id: resolve_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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());
|
assert!(edit_res.is_none());
|
||||||
|
|
||||||
TestData::stop(alpha, beta, gamma)
|
TestData::stop(alpha, beta, gamma)
|
||||||
|
|
Loading…
Reference in a new issue