Added minimal sdk, simplified test using the sdk's client
This commit is contained in:
parent
a75c115761
commit
84784599a7
16 changed files with 379 additions and 83 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -3060,6 +3060,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ubisync-lib",
|
"ubisync-lib",
|
||||||
|
"ubisync-sdk",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3088,6 +3089,15 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ubisync-sdk"
|
name = "ubisync-sdk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"ubisync-lib",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ucd-trie"
|
name = "ucd-trie"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"ubisync",
|
"ubisync",
|
||||||
|
|
|
@ -10,6 +10,7 @@ axum = { version = "0.7.2", features = [ "macros" ] }
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
itertools = "0.12.0"
|
itertools = "0.12.0"
|
||||||
jsonwebtoken = "9.2.0"
|
jsonwebtoken = "9.2.0"
|
||||||
|
reqwest = "0.11.23"
|
||||||
serde = { version = "1.0.166", features = [ "derive" ] }
|
serde = { version = "1.0.166", features = [ "derive" ] }
|
||||||
serde_json = "1.0.99"
|
serde_json = "1.0.99"
|
||||||
serde_with = "3.3.0"
|
serde_with = "3.3.0"
|
||||||
|
|
28
ubisync-lib/src/api/app.rs
Normal file
28
ubisync-lib/src/api/app.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use reqwest::Method;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use super::UbisyncRequest;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AppRegisterRequest {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AppRegisterResponse {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UbisyncRequest for AppRegisterRequest {
|
||||||
|
type PathParameters = ();
|
||||||
|
type Response = AppRegisterResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> reqwest::Method {
|
||||||
|
Method::PUT
|
||||||
|
}
|
||||||
|
fn path(&self, _: Self::PathParameters) -> String {
|
||||||
|
"/app/register".to_string()
|
||||||
|
}
|
||||||
|
}
|
88
ubisync-lib/src/api/element.rs
Normal file
88
ubisync-lib/src/api/element.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use reqwest::Method;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::types::{ElementContent, ElementId, Element};
|
||||||
|
|
||||||
|
use super::UbisyncRequest;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementCreateRequest {
|
||||||
|
pub content: ElementContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementCreateResponse {
|
||||||
|
pub id: ElementId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UbisyncRequest for ElementCreateRequest {
|
||||||
|
type PathParameters = ();
|
||||||
|
type Response = ElementCreateResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::PUT
|
||||||
|
}
|
||||||
|
fn path(&self, _: Self::PathParameters) -> String {
|
||||||
|
"/element".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementGetRequest;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementGetResponse {
|
||||||
|
pub element: Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UbisyncRequest for ElementGetRequest {
|
||||||
|
type PathParameters = ElementId;
|
||||||
|
type Response = ElementGetResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::GET
|
||||||
|
}
|
||||||
|
fn path(&self, params: Self::PathParameters) -> String {
|
||||||
|
format!("/element/{}", params.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementSetRequest {
|
||||||
|
pub content: ElementContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementSetResponse;
|
||||||
|
|
||||||
|
impl UbisyncRequest for ElementSetRequest {
|
||||||
|
type PathParameters = ElementId;
|
||||||
|
type Response = ElementSetResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::POST
|
||||||
|
}
|
||||||
|
fn path(&self, params: Self::PathParameters) -> String {
|
||||||
|
format!("/element/{}", serde_json::to_string(¶ms).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementRemoveRequest;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ElementRemoveResponse;
|
||||||
|
|
||||||
|
impl UbisyncRequest for ElementRemoveRequest {
|
||||||
|
type PathParameters = ElementId;
|
||||||
|
type Response = ElementRemoveResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::DELETE
|
||||||
|
}
|
||||||
|
fn path(&self, params: Self::PathParameters) -> String {
|
||||||
|
format!("/element/{}", serde_json::to_string(¶ms).unwrap())
|
||||||
|
}
|
||||||
|
}
|
23
ubisync-lib/src/api/mod.rs
Normal file
23
ubisync-lib/src/api/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use reqwest::Method;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod app;
|
||||||
|
pub mod element;
|
||||||
|
|
||||||
|
/// Any struct defining a request body for the ubisync API must implement this trait
|
||||||
|
/// It is used both by the client in the SDK and by the API logic in the ubisync node
|
||||||
|
#[async_trait]
|
||||||
|
pub trait UbisyncRequest: for<'de> Deserialize<'de> + Serialize {
|
||||||
|
type PathParameters;
|
||||||
|
type Response: for<'de> Deserialize<'de> + Serialize;
|
||||||
|
|
||||||
|
fn method(&self) -> Method;
|
||||||
|
fn path(&self, params: Self::PathParameters) -> String;
|
||||||
|
async fn parse_response(resp: reqwest::Response) -> Result<Self::Response, reqwest::Error>
|
||||||
|
where
|
||||||
|
for<'de> <Self as UbisyncRequest>::Response: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
resp.json().await
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod api;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod types;
|
|
||||||
pub mod peer;
|
pub mod peer;
|
||||||
|
pub mod types;
|
||||||
|
|
|
@ -6,3 +6,11 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.79"
|
||||||
|
reqwest = { version = "0.11.23", features = [ "json" ] }
|
||||||
|
serde = { version = "1.0.166", features = [ "derive" ] }
|
||||||
|
serde_json = "1.0.99"
|
||||||
|
tracing = "0.1.37"
|
||||||
|
tracing-subscriber = "0.3.17"
|
||||||
|
|
||||||
|
ubisync-lib = { path = "../ubisync-lib" }
|
20
ubisync-sdk/src/error.rs
Normal file
20
ubisync-sdk/src/error.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UbisyncError {
|
||||||
|
InvalidNodeReply(String),
|
||||||
|
AppRegistrationFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UbisyncError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidNodeReply(msg) => write!(f, "Invalid reply from ubisync node: {}", msg),
|
||||||
|
Self::AppRegistrationFailed => write!(f, "Registrating this app at the ubisync node failed."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for UbisyncError {}
|
|
@ -0,0 +1,118 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use error::UbisyncError;
|
||||||
|
use reqwest::{Client, StatusCode};
|
||||||
|
use ubisync_lib::api::{
|
||||||
|
app::{AppRegisterRequest, AppRegisterResponse},
|
||||||
|
UbisyncRequest,
|
||||||
|
};
|
||||||
|
pub use ubisync_lib::*;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub struct UbisyncClient {
|
||||||
|
host: String,
|
||||||
|
port: u16,
|
||||||
|
selected_api_version: String,
|
||||||
|
base_url: String,
|
||||||
|
jwt_token: String,
|
||||||
|
reqwest_client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UbisyncClient {
|
||||||
|
pub async fn init(
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
jwt_token: Option<&str>,
|
||||||
|
application_name: &str,
|
||||||
|
application_description: &str,
|
||||||
|
) -> Result<Self, UbisyncError> {
|
||||||
|
let http_client = Client::new();
|
||||||
|
let mut node_api_versions = http_client
|
||||||
|
.get(&format!("http://{}:{}/versions", host, port))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to contact ubisync node, it may be offline.")
|
||||||
|
.json::<Vec<String>>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to read ubisync node's available API versions.");
|
||||||
|
|
||||||
|
node_api_versions.sort();
|
||||||
|
let selected_version = node_api_versions
|
||||||
|
.get(0)
|
||||||
|
.expect("No available API version returned by ubisync node");
|
||||||
|
|
||||||
|
let token = match jwt_token {
|
||||||
|
Some(t) => t.to_string(),
|
||||||
|
None => {
|
||||||
|
let response = http_client
|
||||||
|
.put(Self::build_base_url(host, port, &selected_version) + "/app/register")
|
||||||
|
.json(&AppRegisterRequest {
|
||||||
|
name: application_name.to_string(),
|
||||||
|
description: application_description.to_string(),
|
||||||
|
})
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("App registration request failed.");
|
||||||
|
if response.status() != StatusCode::OK {
|
||||||
|
return Err(UbisyncError::AppRegistrationFailed);
|
||||||
|
}
|
||||||
|
response
|
||||||
|
.json::<AppRegisterResponse>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to extract JWT from app regstration request")
|
||||||
|
.token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(UbisyncClient {
|
||||||
|
host: host.to_string(),
|
||||||
|
port: port,
|
||||||
|
selected_api_version: selected_version.to_string(),
|
||||||
|
base_url: Self::build_base_url(host, port, selected_version),
|
||||||
|
jwt_token: token.to_string(),
|
||||||
|
reqwest_client: http_client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send<R>(
|
||||||
|
&self,
|
||||||
|
request: R,
|
||||||
|
parameters: R::PathParameters,
|
||||||
|
) -> anyhow::Result<R::Response>
|
||||||
|
where
|
||||||
|
R: UbisyncRequest,
|
||||||
|
{
|
||||||
|
self.reqwest_client
|
||||||
|
.request(
|
||||||
|
request.method(),
|
||||||
|
&(self.base_url.to_owned() + &request.path(parameters)),
|
||||||
|
)
|
||||||
|
.bearer_auth(&self.jwt_token)
|
||||||
|
.json(&request)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.json::<R::Response>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_host(&mut self, host: String) {
|
||||||
|
self.host = host;
|
||||||
|
self.base_url = Self::build_base_url(&self.host, self.port, &self.selected_api_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_port(&mut self, port: u16) {
|
||||||
|
self.port = port;
|
||||||
|
self.base_url = Self::build_base_url(&self.host, self.port, &self.selected_api_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_api_version(&mut self, version: String) {
|
||||||
|
self.selected_api_version = version;
|
||||||
|
self.base_url = Self::build_base_url(&self.host, self.port, &self.selected_api_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_base_url(host: &str, port: u16, api_version: &str) -> String {
|
||||||
|
format!("http://{}:{}/{}", host, port, api_version)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,4 +25,5 @@ ubisync-lib = { path = "../ubisync-lib" }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
reqwest = { version = "0.11.20", features = [ "json" ] }
|
reqwest = { version = "0.11.20", features = [ "json" ] }
|
||||||
|
ubisync-sdk = { path = "../ubisync-sdk" }
|
|
@ -1,4 +1,4 @@
|
||||||
use axum::Router;
|
use axum::{Router, routing::get, response::{Response, IntoResponse}, http::StatusCode, Json};
|
||||||
use tokio::{net::TcpListener, task::JoinHandle};
|
use tokio::{net::TcpListener, task::JoinHandle};
|
||||||
|
|
||||||
use crate::{config::ApiConfig, state::ApiState};
|
use crate::{config::ApiConfig, state::ApiState};
|
||||||
|
@ -44,6 +44,8 @@ impl From<ApiConfig> for ApiBuilder {
|
||||||
impl ApiBuilder {
|
impl ApiBuilder {
|
||||||
pub async fn build(&self, state: ApiState) -> Api {
|
pub async fn build(&self, state: ApiState) -> Api {
|
||||||
let mut app: Router = Router::new();
|
let mut app: Router = Router::new();
|
||||||
|
app = app.route("/versions", get(list_available_versions));
|
||||||
|
|
||||||
match &self.version {
|
match &self.version {
|
||||||
Some(v) if v == "v0" => app = app.nest(&format!("/{}", v), v0::get_router(state)),
|
Some(v) if v == "v0" => app = app.nest(&format!("/{}", v), v0::get_router(state)),
|
||||||
_ => app = app.nest("/v0", v0::get_router(state)),
|
_ => app = app.nest("/v0", v0::get_router(state)),
|
||||||
|
@ -72,3 +74,8 @@ impl ApiBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn list_available_versions() -> Response {
|
||||||
|
(StatusCode::OK, Json {0: vec!["v0"]}).into_response()
|
||||||
|
}
|
|
@ -11,11 +11,11 @@ use axum::{
|
||||||
use jsonwebtoken::{decode, Header};
|
use jsonwebtoken::{decode, Header};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
use ubisync_lib::api::app::{AppRegisterRequest, AppRegisterResponse};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::state::ApiState;
|
use crate::state::ApiState;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct AppId(Uuid);
|
pub struct AppId(Uuid);
|
||||||
|
|
||||||
|
@ -30,15 +30,6 @@ struct JWTClaims {
|
||||||
sub: AppId,
|
sub: AppId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct AppDescription {
|
|
||||||
pub name: String,
|
|
||||||
pub desc_text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub(super) async fn auth(
|
pub(super) async fn auth(
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
mut request: Request<Body>,
|
mut request: Request<Body>,
|
||||||
|
@ -67,12 +58,12 @@ pub(super) async fn auth(
|
||||||
|
|
||||||
pub(super) async fn register(
|
pub(super) async fn register(
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
Json(data): Json<AppDescription>,
|
Json(body): Json<AppRegisterRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
// Maybe ask for consent by user
|
// Maybe ask for consent by user
|
||||||
|
|
||||||
// If user wants registration, proceed
|
// If user wants registration, proceed
|
||||||
let result = s.add_app(&data);
|
let result = s.add_app(&body.name, &body.description);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
|
@ -83,7 +74,13 @@ pub(super) async fn register(
|
||||||
&s.jwt_encoding_key(),
|
&s.jwt_encoding_key(),
|
||||||
);
|
);
|
||||||
match jwt {
|
match jwt {
|
||||||
Ok(token) => (StatusCode::OK, token).into_response(),
|
Ok(token) => (
|
||||||
|
StatusCode::OK,
|
||||||
|
Json {
|
||||||
|
0: AppRegisterResponse { token: token },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to encode token: {:?}", e);
|
warn!("Failed to encode token: {:?}", e);
|
||||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
|
|
@ -6,17 +6,28 @@ use axum::{
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use tracing::{debug, warn};
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::state::ApiState;
|
use crate::state::ApiState;
|
||||||
use ubisync_lib::types::{ElementContent, ElementId};
|
use ubisync_lib::{
|
||||||
|
api::element::{
|
||||||
|
ElementCreateRequest, ElementCreateResponse, ElementGetResponse, ElementSetRequest,
|
||||||
|
ElementSetResponse, ElementRemoveResponse,
|
||||||
|
},
|
||||||
|
types::ElementId,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
||||||
let element = s.get_element(&id);
|
let element = s.get_element(&id);
|
||||||
match element {
|
match element {
|
||||||
Ok(el) => (StatusCode::OK, Json { 0: el }).into_response(),
|
Ok(el) => (
|
||||||
Err(e) => {
|
StatusCode::OK,
|
||||||
warn!("Element not found:\n{:?}", e);
|
Json {
|
||||||
|
0: ElementGetResponse { element: el },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
Err(_) => {
|
||||||
StatusCode::NOT_FOUND.into_response()
|
StatusCode::NOT_FOUND.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,15 +35,15 @@ pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>)
|
||||||
|
|
||||||
pub(super) async fn create(
|
pub(super) async fn create(
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
Json(content): Json<ElementContent>,
|
Json(req): Json<ElementCreateRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let element_id = s.create_element(&content);
|
let element_id = s.create_element(&req.content);
|
||||||
debug!("{:?}", element_id);
|
debug!("{:?}", element_id);
|
||||||
match element_id {
|
match element_id {
|
||||||
Ok(id) => (
|
Ok(id) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
Json {
|
Json {
|
||||||
0: &Into::<String>::into(&id),
|
0: ElementCreateResponse { id },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
@ -43,11 +54,17 @@ pub(super) async fn create(
|
||||||
pub(super) async fn set(
|
pub(super) async fn set(
|
||||||
Path(id): Path<ElementId>,
|
Path(id): Path<ElementId>,
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
Json(content): Json<ElementContent>,
|
Json(req): Json<ElementSetRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let res = s.write_element_content(&id, &content);
|
let res = s.write_element_content(&id, &req.content);
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => StatusCode::OK.into_response(),
|
Ok(_) => (
|
||||||
|
StatusCode::OK,
|
||||||
|
Json {
|
||||||
|
0: ElementSetResponse,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +72,7 @@ pub(super) async fn set(
|
||||||
pub(super) async fn remove(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
pub(super) async fn remove(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
||||||
let res = s.remove_element(&id);
|
let res = s.remove_element(&id);
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => StatusCode::OK.into_response(),
|
Ok(_) => (StatusCode::OK, Json { 0: ElementRemoveResponse }).into_response(),
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use jsonwebtoken::{DecodingKey, EncodingKey, Validation};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use ubisync_lib::{types::{ElementContent, ElementId, Element}, messages::MessageContent};
|
use ubisync_lib::{types::{ElementContent, ElementId, Element}, messages::MessageContent};
|
||||||
|
|
||||||
use crate::{api::v0::app::{AppDescription, AppId}, state::queries};
|
use crate::{api::v0::app::AppId, state::queries};
|
||||||
|
|
||||||
use super::State;
|
use super::State;
|
||||||
|
|
||||||
|
@ -29,15 +29,15 @@ impl ApiState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_app(&self, description: &AppDescription) -> anyhow::Result<AppId> {
|
pub fn add_app(&self, name: &str, description: &str) -> anyhow::Result<AppId> {
|
||||||
let id = AppId::new();
|
let id = AppId::new();
|
||||||
let last_access = Utc::now();
|
let last_access = Utc::now();
|
||||||
queries::apps::add(
|
queries::apps::add(
|
||||||
self.db(),
|
self.db(),
|
||||||
&id,
|
&id,
|
||||||
&last_access,
|
&last_access,
|
||||||
&description.name,
|
name,
|
||||||
&description.desc_text,
|
description,
|
||||||
)?;
|
)?;
|
||||||
debug!("Successfully added app");
|
debug!("Successfully added app");
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use tracing::{debug, Level};
|
use tracing::{debug, warn, Level};
|
||||||
use ubisync::{config::Config, Ubisync, api::v0::app::AppDescription};
|
use ubisync::{config::Config, Ubisync};
|
||||||
use ubisync_lib::types::{ElementContent, ElementId, Element};
|
use ubisync_lib::{
|
||||||
|
api::element::{ElementCreateRequest, ElementGetRequest},
|
||||||
|
types::{Element, ElementContent},
|
||||||
|
};
|
||||||
|
use ubisync_sdk::UbisyncClient;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn two_nodes_element_creation() {
|
async fn two_nodes_element_creation() {
|
||||||
|
@ -11,6 +14,7 @@ async fn two_nodes_element_creation() {
|
||||||
.pretty()
|
.pretty()
|
||||||
.with_max_level(Level::DEBUG)
|
.with_max_level(Level::DEBUG)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// Two nodes need to bind to different ports
|
// Two nodes need to bind to different ports
|
||||||
let mut c2 = Config::default();
|
let mut c2 = Config::default();
|
||||||
c2.api_config.port = Some(9982);
|
c2.api_config.port = Some(9982);
|
||||||
|
@ -19,63 +23,35 @@ async fn two_nodes_element_creation() {
|
||||||
ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into())
|
ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let http_client = reqwest::Client::new();
|
let api_client1 = UbisyncClient::init("localhost", 9981, None, "App", "Long desc")
|
||||||
let register_response = http_client
|
|
||||||
.put("http://localhost:9981/v0/app/register")
|
|
||||||
.json(&AppDescription {
|
|
||||||
name: "Test".to_string(),
|
|
||||||
desc_text: "desc".to_string(),
|
|
||||||
})
|
|
||||||
.send()
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let jwt1 = register_response
|
let api_client2 = UbisyncClient::init("localhost", 9982, None, "App", "Long desc")
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.expect("Couldn't fetch token from response");
|
|
||||||
let register_response = http_client
|
|
||||||
.put("http://localhost:9982/v0/app/register")
|
|
||||||
.json(&AppDescription {
|
|
||||||
name: "Test".to_string(),
|
|
||||||
desc_text: "desc".to_string(),
|
|
||||||
})
|
|
||||||
.send()
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let jwt2 = register_response
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.expect("Couldn't fetch token from response");
|
|
||||||
|
|
||||||
let test_element_content = ElementContent::Text("Text".to_string());
|
let test_element_content = ElementContent::Text("Text".to_string());
|
||||||
let put_resp = http_client
|
let create_resp = api_client1
|
||||||
.put(&format!("http://localhost:9981/v0/element"))
|
.send(
|
||||||
.json(&test_element_content)
|
ElementCreateRequest {
|
||||||
.header("Authorization", &format!("Bearer {}", &jwt1))
|
content: test_element_content.clone(),
|
||||||
.send()
|
},
|
||||||
|
(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("{:?}", &put_resp);
|
let id = create_resp.id;
|
||||||
let put_resp_text = put_resp.text().await.expect("No put response body");
|
|
||||||
debug!("{}", put_resp_text);
|
|
||||||
let id =
|
|
||||||
serde_json::from_str::<ElementId>(&put_resp_text).expect("Could not deserialize ElementId");
|
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(3000)).await;
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
let get_resp = http_client
|
let mut get_resp = api_client2.send(ElementGetRequest {}, id.clone()).await;
|
||||||
.get(&format!(
|
while let Err(_) = get_resp {
|
||||||
"http://localhost:9982/v0/element/{}",
|
warn!("Sleeping for another second, element has not arrived yet");
|
||||||
Into::<String>::into(&id)
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
))
|
get_resp = api_client2.send(ElementGetRequest {}, id.clone()).await;
|
||||||
.header("Authorization", &format!("Bearer {}", &jwt2))
|
}
|
||||||
.send()
|
|
||||||
.await
|
let received_element: Element = get_resp.unwrap().element;
|
||||||
.expect("Get request failed");
|
|
||||||
let get_resp_text = get_resp.text().await.expect("No get request body");
|
|
||||||
debug!("{}", get_resp_text);
|
|
||||||
let received_element =
|
|
||||||
serde_json::from_str::<Element>(&get_resp_text).expect("Could not deserialize Element");
|
|
||||||
debug!("Other node received this element: {:?}", received_element);
|
debug!("Other node received this element: {:?}", received_element);
|
||||||
|
|
||||||
assert_eq!(&test_element_content, received_element.content());
|
assert_eq!(&test_element_content, received_element.content());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue