2024-01-18 11:18:17 +00:00
|
|
|
use crate::backend::api::article::{CreateArticleData, EditArticleData, ForkArticleData};
|
2024-01-17 16:14:16 +00:00
|
|
|
use crate::backend::api::instance::FollowInstance;
|
|
|
|
use crate::backend::api::{ResolveObject, SearchArticleData};
|
|
|
|
use crate::backend::database::conflict::ApiConflict;
|
|
|
|
use crate::backend::database::instance::{DbInstance, InstanceView};
|
2024-01-16 15:07:01 +00:00
|
|
|
use crate::common::LocalUserView;
|
|
|
|
use crate::common::{ArticleView, LoginUserData, RegisterUserData};
|
2024-01-17 16:14:16 +00:00
|
|
|
use crate::common::{DbArticle, GetArticleData};
|
2024-01-12 15:48:24 +00:00
|
|
|
use crate::frontend::error::MyResult;
|
2024-01-03 16:06:52 +00:00
|
|
|
use anyhow::anyhow;
|
|
|
|
use once_cell::sync::Lazy;
|
2024-01-17 16:14:16 +00:00
|
|
|
use reqwest::{Client, RequestBuilder, StatusCode};
|
2024-01-03 16:06:52 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-01-17 16:14:16 +00:00
|
|
|
use url::Url;
|
2024-01-03 16:06:52 +00:00
|
|
|
|
|
|
|
pub static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
|
|
|
|
2024-01-17 15:40:01 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct ApiClient {
|
2024-01-18 11:18:17 +00:00
|
|
|
client: Client,
|
2024-01-17 15:40:01 +00:00
|
|
|
pub hostname: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ApiClient {
|
|
|
|
pub fn new(client: Client, hostname: String) -> Self {
|
|
|
|
Self { client, hostname }
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_query<T, R>(&self, endpoint: &str, query: Option<R>) -> MyResult<T>
|
|
|
|
where
|
|
|
|
T: for<'de> Deserialize<'de>,
|
|
|
|
R: Serialize,
|
|
|
|
{
|
|
|
|
let mut req = self
|
|
|
|
.client
|
|
|
|
.get(format!("http://{}/api/v1/{}", &self.hostname, endpoint));
|
|
|
|
if let Some(query) = query {
|
|
|
|
req = req.query(&query);
|
|
|
|
}
|
|
|
|
handle_json_res::<T>(req).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_article(&self, data: GetArticleData) -> MyResult<ArticleView> {
|
|
|
|
self.get_query::<ArticleView, _>("article", Some(data))
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn register(&self, register_form: RegisterUserData) -> MyResult<LocalUserView> {
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.post(format!("http://{}/api/v1/account/register", self.hostname))
|
|
|
|
.form(®ister_form);
|
|
|
|
handle_json_res::<LocalUserView>(req).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn login(&self, login_form: LoginUserData) -> MyResult<LocalUserView> {
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.post(format!("http://{}/api/v1/account/login", self.hostname))
|
|
|
|
.form(&login_form);
|
|
|
|
handle_json_res::<LocalUserView>(req).await
|
|
|
|
}
|
2024-01-17 16:14:16 +00:00
|
|
|
|
|
|
|
pub async fn create_article(&self, title: String, new_text: String) -> MyResult<ArticleView> {
|
|
|
|
let create_form = CreateArticleData {
|
|
|
|
title: title.clone(),
|
|
|
|
};
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.post(format!("http://{}/api/v1/article", &self.hostname))
|
|
|
|
.form(&create_form);
|
|
|
|
let article: ArticleView = handle_json_res(req).await?;
|
|
|
|
|
|
|
|
// create initial edit to ensure that conflicts are generated (there are no conflicts on empty file)
|
|
|
|
// TODO: maybe take initial text directly in create article, no reason to have empty article
|
|
|
|
let edit_form = EditArticleData {
|
|
|
|
article_id: article.article.id,
|
|
|
|
new_text,
|
|
|
|
previous_version_id: article.latest_version,
|
|
|
|
resolve_conflict_id: None,
|
|
|
|
};
|
|
|
|
Ok(self.edit_article(&edit_form).await.unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn edit_article_with_conflict(
|
|
|
|
&self,
|
|
|
|
edit_form: &EditArticleData,
|
|
|
|
) -> MyResult<Option<ApiConflict>> {
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.patch(format!("http://{}/api/v1/article", self.hostname))
|
|
|
|
.form(edit_form);
|
|
|
|
handle_json_res(req).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn edit_article(&self, edit_form: &EditArticleData) -> MyResult<ArticleView> {
|
|
|
|
let edit_res = self.edit_article_with_conflict(edit_form).await?;
|
|
|
|
assert!(edit_res.is_none());
|
|
|
|
|
|
|
|
self.get_article(GetArticleData {
|
|
|
|
title: None,
|
|
|
|
instance_id: None,
|
|
|
|
id: Some(edit_form.article_id),
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn search(&self, search_form: &SearchArticleData) -> MyResult<Vec<DbArticle>> {
|
|
|
|
self.get_query("search", Some(search_form)).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_local_instance(&self) -> MyResult<InstanceView> {
|
|
|
|
self.get_query("instance", None::<i32>).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn follow_instance(&self, follow_instance: &str) -> MyResult<DbInstance> {
|
|
|
|
// fetch beta instance on alpha
|
|
|
|
let resolve_form = ResolveObject {
|
|
|
|
id: Url::parse(&format!("http://{}", follow_instance))?,
|
|
|
|
};
|
|
|
|
let instance_resolved: DbInstance =
|
|
|
|
get_query(&self.hostname, "instance/resolve", Some(resolve_form)).await?;
|
|
|
|
|
|
|
|
// send follow
|
|
|
|
let follow_form = FollowInstance {
|
|
|
|
id: instance_resolved.id,
|
|
|
|
};
|
|
|
|
// cant use post helper because follow doesnt return json
|
|
|
|
let res = self
|
|
|
|
.client
|
|
|
|
.post(format!("http://{}/api/v1/instance/follow", self.hostname))
|
|
|
|
.form(&follow_form)
|
|
|
|
.send()
|
|
|
|
.await?;
|
|
|
|
if res.status() == StatusCode::OK {
|
|
|
|
Ok(instance_resolved)
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("API error: {}", res.text().await?).into())
|
|
|
|
}
|
|
|
|
}
|
2024-01-18 11:18:17 +00:00
|
|
|
|
|
|
|
pub async fn my_profile(&self) -> MyResult<LocalUserView> {
|
|
|
|
let req = self.client.get(format!(
|
|
|
|
"http://{}/api/v1/account/my_profile",
|
|
|
|
self.hostname
|
|
|
|
));
|
|
|
|
handle_json_res::<LocalUserView>(req).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn logout(&self) -> MyResult<()> {
|
|
|
|
self.client
|
|
|
|
.get(format!("http://{}/api/v1/account/logout", self.hostname))
|
|
|
|
.send()
|
|
|
|
.await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn fork_article(&self, form: &ForkArticleData) -> MyResult<ArticleView> {
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.post(format!("http://{}/api/v1/article/fork", self.hostname))
|
|
|
|
.form(form);
|
|
|
|
Ok(handle_json_res(req).await.unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_conflicts(&self) -> MyResult<Vec<ApiConflict>> {
|
|
|
|
let req = self
|
|
|
|
.client
|
|
|
|
.get(format!("http://{}/api/v1/edit_conflicts", &self.hostname));
|
|
|
|
Ok(handle_json_res(req).await.unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn resolve_article(&self, id: Url) -> MyResult<ArticleView> {
|
|
|
|
let resolve_object = ResolveObject { id };
|
|
|
|
get_query(&self.hostname, "article/resolve", Some(resolve_object)).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn resolve_instance(&self, id: Url) -> MyResult<DbInstance> {
|
|
|
|
let resolve_object = ResolveObject { id };
|
|
|
|
get_query(&self.hostname, "instance/resolve", Some(resolve_object)).await
|
|
|
|
}
|
2024-01-03 16:06:52 +00:00
|
|
|
}
|
|
|
|
|
2024-01-18 11:18:17 +00:00
|
|
|
async fn get_query<T, R>(hostname: &str, endpoint: &str, query: Option<R>) -> MyResult<T>
|
2024-01-03 16:06:52 +00:00
|
|
|
where
|
|
|
|
T: for<'de> Deserialize<'de>,
|
|
|
|
R: Serialize,
|
|
|
|
{
|
|
|
|
let mut req = CLIENT.get(format!("http://{}/api/v1/{}", hostname, endpoint));
|
|
|
|
if let Some(query) = query {
|
|
|
|
req = req.query(&query);
|
|
|
|
}
|
|
|
|
handle_json_res::<T>(req).await
|
|
|
|
}
|
|
|
|
|
2024-01-18 11:18:17 +00:00
|
|
|
async fn handle_json_res<T>(req: RequestBuilder) -> MyResult<T>
|
2024-01-03 16:06:52 +00:00
|
|
|
where
|
|
|
|
T: for<'de> Deserialize<'de>,
|
|
|
|
{
|
2024-01-11 15:50:57 +00:00
|
|
|
let res = req.send().await?;
|
2024-01-03 16:06:52 +00:00
|
|
|
let status = res.status();
|
2024-01-11 15:50:57 +00:00
|
|
|
let text = res.text().await?;
|
2024-01-03 16:06:52 +00:00
|
|
|
if status == reqwest::StatusCode::OK {
|
2024-01-12 15:48:24 +00:00
|
|
|
Ok(serde_json::from_str(&text).map_err(|e| anyhow!("Json error on {text}: {e}"))?)
|
2024-01-03 16:06:52 +00:00
|
|
|
} else {
|
2024-01-11 15:50:57 +00:00
|
|
|
Err(anyhow!("API error: {text}").into())
|
2024-01-03 16:06:52 +00:00
|
|
|
}
|
|
|
|
}
|