Refactoring, mainly formatting

This commit is contained in:
Philip (a-0) 2024-01-05 20:48:23 +01:00
parent 3825263fa3
commit d8f1733eb3
25 changed files with 667 additions and 348 deletions

View file

@ -1,28 +1,9 @@
use axum::Router; use axum::Router;
use tokio::{net::TcpListener, task::JoinHandle}; use tokio::{net::TcpListener, task::JoinHandle};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::{state::ApiState, config::ApiConfig}; use crate::{config::ApiConfig, state::ApiState};
mod v0; pub mod v0;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AppId(Uuid);
impl AppId {
pub fn new() -> Self {
AppId { 0: Uuid::new_v4() }
}
}
#[derive(Serialize, Deserialize)]
pub struct AppDescription {
pub name: String,
pub desc_text: String,
}
pub struct Api { pub struct Api {
server_thread: JoinHandle<()>, server_thread: JoinHandle<()>,
@ -42,7 +23,11 @@ pub struct ApiBuilder {
impl Default for ApiBuilder { impl Default for ApiBuilder {
fn default() -> Self { fn default() -> Self {
ApiBuilder { bind_ip: None, port: None, version: None } ApiBuilder {
bind_ip: None,
port: None,
version: None,
}
} }
} }
@ -51,7 +36,7 @@ impl From<ApiConfig> for ApiBuilder {
ApiBuilder { ApiBuilder {
bind_ip: value.ip, bind_ip: value.ip,
port: value.port, port: value.port,
version: value.version version: value.version,
} }
} }
} }
@ -72,12 +57,18 @@ impl ApiBuilder {
Some(p) => p, Some(p) => p,
None => &9981, None => &9981,
}; };
let listener = TcpListener::bind(&format!("{}:{}", ip, port)).await.unwrap(); let listener = TcpListener::bind(&format!("{}:{}", ip, port))
.await
let task_handle = tokio::spawn({ async move { .unwrap();
axum::serve(listener, app).await.unwrap();
}});
Api{ server_thread: task_handle } let task_handle = tokio::spawn({
async move {
axum::serve(listener, app).await.unwrap();
}
});
Api {
server_thread: task_handle,
}
} }
} }

View file

@ -1,11 +1,29 @@
use std::sync::Arc; use std::sync::Arc;
use serde::{Serialize, Deserialize}; use axum::{
use axum::{Extension, extract::Request, body::Body, middleware::Next, response::{IntoResponse, Response}, http::{header::AUTHORIZATION, StatusCode}, Json}; body::Body,
extract::Request,
http::{header::AUTHORIZATION, StatusCode},
middleware::Next,
response::{IntoResponse, Response},
Extension, Json,
};
use jsonwebtoken::{decode, Header}; use jsonwebtoken::{decode, Header};
use tracing::{debug, warn, error}; use serde::{Deserialize, Serialize};
use tracing::{debug, error, warn};
use uuid::Uuid;
use crate::{state::ApiState, api::{AppId, AppDescription}}; use crate::state::ApiState;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AppId(Uuid);
impl AppId {
pub fn new() -> Self {
AppId { 0: Uuid::new_v4() }
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct JWTClaims { struct JWTClaims {
@ -13,7 +31,19 @@ struct JWTClaims {
} }
pub(super) async fn auth(s: Extension<Arc<ApiState>>, mut request: Request<Body>, next: Next) -> Response {
#[derive(Serialize, Deserialize)]
pub struct AppDescription {
pub name: String,
pub desc_text: String,
}
pub(super) async fn auth(
s: Extension<Arc<ApiState>>,
mut request: Request<Body>,
next: Next,
) -> Response {
if let Some(auth_header) = request.headers().get(AUTHORIZATION) { if let Some(auth_header) = request.headers().get(AUTHORIZATION) {
if let Ok(header_string) = auth_header.to_str() { if let Ok(header_string) = auth_header.to_str() {
if header_string.starts_with("Bearer") { if header_string.starts_with("Bearer") {
@ -35,9 +65,10 @@ pub(super) async fn auth(s: Extension<Arc<ApiState>>, mut request: Request<Body>
StatusCode::UNAUTHORIZED.into_response() StatusCode::UNAUTHORIZED.into_response()
} }
pub(super) async fn register(
s: Extension<Arc<ApiState>>,
pub(super) async fn register(s: Extension<Arc<ApiState>>, Json(data): Json<AppDescription>) -> Response { Json(data): Json<AppDescription>,
) -> Response {
// Maybe ask for consent by user // Maybe ask for consent by user
// If user wants registration, proceed // If user wants registration, proceed
@ -48,7 +79,7 @@ pub(super) async fn register(s: Extension<Arc<ApiState>>, Json(data): Json<AppDe
// Build JWT, respond // Build JWT, respond
let jwt = jsonwebtoken::encode( let jwt = jsonwebtoken::encode(
&Header::default(), &Header::default(),
&JWTClaims {sub: id}, &JWTClaims { sub: id },
&s.jwt_encoding_key(), &s.jwt_encoding_key(),
); );
match jwt { match jwt {
@ -58,10 +89,10 @@ pub(super) async fn register(s: Extension<Arc<ApiState>>, Json(data): Json<AppDe
StatusCode::INTERNAL_SERVER_ERROR.into_response() StatusCode::INTERNAL_SERVER_ERROR.into_response()
} }
} }
}, }
Err(e) => { Err(e) => {
error!("Failed to register new application! {:?}", e); error!("Failed to register new application! {:?}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response() StatusCode::INTERNAL_SERVER_ERROR.into_response()
}, }
} }
} }

View file

@ -1,15 +1,22 @@
use std::sync::Arc; use std::sync::Arc;
use axum::{extract::{Path, Json}, Extension, response::{IntoResponse, Response}, http::StatusCode}; use axum::{
use tracing::{warn, debug}; extract::{Json, Path},
http::StatusCode,
use crate::state::{types::{ElementId, ElementContent}, ApiState}; response::{IntoResponse, Response},
Extension,
};
use tracing::{debug, warn};
use crate::state::{
types::{ElementContent, ElementId},
ApiState,
};
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) => (StatusCode::OK, Json { 0: el }).into_response(),
Err(e) => { Err(e) => {
warn!("Element not found:\n{:?}", e); warn!("Element not found:\n{:?}", e);
StatusCode::NOT_FOUND.into_response() StatusCode::NOT_FOUND.into_response()
@ -17,16 +24,29 @@ pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>)
} }
} }
pub(super) async fn create(s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response { pub(super) async fn create(
s: Extension<Arc<ApiState>>,
Json(content): Json<ElementContent>,
) -> Response {
let element_id = s.create_element(&content); let element_id = s.create_element(&content);
debug!("{:?}", element_id); debug!("{:?}", element_id);
match element_id { match element_id {
Ok(id) => (StatusCode::OK, Json{0: &Into::<String>::into(&id)}).into_response(), Ok(id) => (
StatusCode::OK,
Json {
0: &Into::<String>::into(&id),
},
)
.into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
} }
} }
pub(super) async fn set(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response { pub(super) async fn set(
Path(id): Path<ElementId>,
s: Extension<Arc<ApiState>>,
Json(content): Json<ElementContent>,
) -> Response {
let res = s.write_element_content(&id, &content); let res = s.write_element_content(&id, &content);
match res { match res {
Ok(_) => StatusCode::OK.into_response(), Ok(_) => StatusCode::OK.into_response(),
@ -40,4 +60,4 @@ pub(super) async fn remove(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>
Ok(_) => StatusCode::OK.into_response(), Ok(_) => StatusCode::OK.into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
} }
} }

View file

@ -1,20 +1,25 @@
use axum::{
middleware::from_fn,
routing::{get, put},
Extension, Router,
};
use std::sync::Arc; use std::sync::Arc;
use axum::{Router, routing::{put, get}, Extension, middleware::from_fn};
use crate::state::ApiState; use crate::state::ApiState;
mod app; pub mod app;
mod element; pub mod element;
pub fn get_router(state: ApiState) -> Router { pub fn get_router(state: ApiState) -> Router {
Router::new() Router::new()
// authenticated routes // authenticated routes
.route("/element", put(element::create)) .route("/element", put(element::create))
.route("/element/:id", get(element::get).post(element::set).delete(element::remove)) .route(
"/element/:id",
get(element::get).post(element::set).delete(element::remove),
)
.layer(from_fn(app::auth)) .layer(from_fn(app::auth))
// public / unauthenticated routes // public / unauthenticated routes
.route("/app/register", put(app::register)) .route("/app/register", put(app::register))
.layer(Extension(Arc::new(state))) .layer(Extension(Arc::new(state)))
} }

View file

@ -4,22 +4,24 @@ use crate::state::{types::PeerId, CommState};
use super::messages::{Message, MessageContent}; use super::messages::{Message, MessageContent};
pub fn handle(state: &CommState, peer: &PeerId, message: Message) { pub fn handle(state: &CommState, peer: &PeerId, message: Message) {
debug!("Handling message now: {:?}", message); debug!("Handling message now: {:?}", message);
match message.content() { match message.content() {
MessageContent::Hello { peer_name } => { MessageContent::Hello { peer_name } => {
state.set_peer(peer, peer_name).expect("State failed"); state.set_peer(peer, peer_name).expect("State failed");
}, }
MessageContent::CreateElement { id, content } => { MessageContent::CreateElement { id, content } => {
state.add_received_element(id, content, message.id()).expect("State failed"); state
}, .add_received_element(id, content, message.id())
.expect("State failed");
}
MessageContent::SetElement { id, content } => { MessageContent::SetElement { id, content } => {
state.update_element_content(id, content, message.id()).expect("State failed"); state
}, .update_element_content(id, content, message.id())
.expect("State failed");
}
MessageContent::RemoveElement { id } => { MessageContent::RemoveElement { id } => {
state.remove_element(id).expect("State failed"); state.remove_element(id).expect("State failed");
} }
} }
} }

View file

@ -1,7 +1,6 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use crate::state::types::{MessageId, ElementId, ElementContent};
use crate::state::types::{ElementContent, ElementId, MessageId};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message { pub struct Message {
@ -10,7 +9,6 @@ pub struct Message {
content: MessageContent, content: MessageContent,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum MessageContent { pub enum MessageContent {
Hello { Hello {
@ -26,19 +24,17 @@ pub enum MessageContent {
}, },
RemoveElement { RemoveElement {
id: ElementId, id: ElementId,
} },
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MessageSignature {} pub struct MessageSignature {}
impl Message { impl Message {
pub fn new(content: MessageContent) -> Self { pub fn new(content: MessageContent) -> Self {
Message { Message {
id: MessageId::new(), id: MessageId::new(),
signature: MessageSignature { }, signature: MessageSignature {},
content: content, content: content,
} }
} }
@ -56,4 +52,4 @@ impl MessageSignature {
pub fn verify(&self, _content: &MessageContent) -> bool { pub fn verify(&self, _content: &MessageContent) -> bool {
true true
} }
} }

View file

@ -1,23 +1,23 @@
pub mod messages;
pub mod message_processor; pub mod message_processor;
pub mod messages;
mod types; mod types;
use tracing::{warn, error, debug}; use tracing::{debug, error, warn};
pub use types::*; pub use types::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{Read, Write};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::io::{Read, Write};
use anyhow::bail; use anyhow::bail;
use i2p::net::{I2pListenerBuilder, I2pListener, I2pSocketAddr, I2pStream, I2pAddr}; use i2p::net::{I2pAddr, I2pListener, I2pListenerBuilder, I2pSocketAddr, I2pStream};
use i2p::sam_options::SAMOptions; use i2p::sam_options::SAMOptions;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::Config;
use crate::state::CommState;
use crate::state::types::PeerId; use crate::state::types::PeerId;
use crate::state::CommState;
use crate::Config;
use self::messages::Message; use self::messages::Message;
@ -31,16 +31,14 @@ pub struct CommHandle {
impl CommHandle { impl CommHandle {
pub fn new(state: CommState, config: &Config) -> anyhow::Result<Self> { pub fn new(state: CommState, config: &Config) -> anyhow::Result<Self> {
let mut listener_builder = I2pListenerBuilder::default() let mut listener_builder =
.with_options(SAMOptions::default()); I2pListenerBuilder::default().with_options(SAMOptions::default());
if let Some(privkey) = &config.i2p_private_key { if let Some(privkey) = &config.i2p_private_key {
listener_builder = listener_builder.with_private_key(privkey.to_string()); listener_builder = listener_builder.with_private_key(privkey.to_string());
} }
let listener = listener_builder let listener = listener_builder.build().unwrap();
.build()
.unwrap();
Ok(CommHandle { Ok(CommHandle {
state: Arc::new(state), state: Arc::new(state),
@ -71,7 +69,9 @@ impl CommHandle {
// there is another stream - thus, the existing streams will not be read. // there is another stream - thus, the existing streams will not be read.
// `spawn_blocking` moves the reading task to a special pool of tasks which are // `spawn_blocking` moves the reading task to a special pool of tasks which are
// executed _despite_ other tasks blocking for something. // executed _despite_ other tasks blocking for something.
tokio::task::spawn_blocking(move || Self::read_connection(wrapped_stream, state_arc)); tokio::task::spawn_blocking(move || {
Self::read_connection(wrapped_stream, state_arc)
});
} }
} }
} }
@ -90,11 +90,14 @@ impl CommHandle {
for peer in self.state.get_peers().unwrap() { for peer in self.state.get_peers().unwrap() {
debug!("Sending to peer '{:?}' message '{:?}'", &peer, &msg); debug!("Sending to peer '{:?}' message '{:?}'", &peer, &msg);
if let Err(e) = self.send_to_addr(&peer.addr(), msg_string.as_bytes()).await { if let Err(e) = self.send_to_addr(&peer.addr(), msg_string.as_bytes()).await {
debug!("Failed to send message.\nError: {:?}\nMessage: {:?}", e, &msg); debug!(
"Failed to send message.\nError: {:?}\nMessage: {:?}",
e, &msg
);
} }
} }
Ok(()) Ok(())
}, }
Err(e) => bail!(e), Err(e) => bail!(e),
} }
} }
@ -104,7 +107,7 @@ impl CommHandle {
Ok(msg_string) => { Ok(msg_string) => {
self.send_to_addr(dest, msg_string.as_bytes()).await?; self.send_to_addr(dest, msg_string.as_bytes()).await?;
Ok(()) Ok(())
}, }
Err(e) => bail!(e), Err(e) => bail!(e),
} }
} }
@ -116,12 +119,14 @@ impl CommHandle {
Ok(client) => { Ok(client) => {
//client.inner.sam.conn.set_nodelay(true)?; //client.inner.sam.conn.set_nodelay(true)?;
//client.inner.sam.conn.set_nonblocking(false)?; //client.inner.sam.conn.set_nonblocking(false)?;
self.clients.write().await.insert(addr.clone(), Arc::new(RwLock::new(client))); self.clients
}, .write()
.await
.insert(addr.clone(), Arc::new(RwLock::new(client)));
}
Err(e) => bail!(e), Err(e) => bail!(e),
} }
} }
// Fetch current client for this connection from clients map, and send the message // Fetch current client for this connection from clients map, and send the message
if let Some(client) = self.clients.read().await.get(&addr) { if let Some(client) = self.clients.read().await.get(&addr) {
@ -129,18 +134,21 @@ impl CommHandle {
match writeguard.write_all(msg) { match writeguard.write_all(msg) {
Ok(_) => { Ok(_) => {
writeguard.flush()?; writeguard.flush()?;
return Ok(()) return Ok(());
}, }
Err(e) => { Err(e) => {
warn!("Error writing to stream: {}", e) warn!("Error writing to stream: {}", e)
} }
} }
} } else {
else { return Err(anyhow::Error::msg(
return Err(anyhow::Error::msg("No client found despite trying to add one beforehand.")) "No client found despite trying to add one beforehand.",
));
} }
self.clients.write().await.remove(&addr); self.clients.write().await.remove(&addr);
Err(anyhow::Error::msg("Failed to send anything, most likely the stream was broken and has been removed")) Err(anyhow::Error::msg(
"Failed to send anything, most likely the stream was broken and has been removed",
))
} }
pub fn i2p_address(&self) -> anyhow::Result<I2pSocketAddr> { pub fn i2p_address(&self) -> anyhow::Result<I2pSocketAddr> {
@ -156,27 +164,29 @@ impl CommHandle {
Ok(i2p_dest) Ok(i2p_dest)
} }
fn read_connection(wrapped_stream: Arc<RwLock<I2pStream>>, state: Arc<CommState>) -> JoinHandle<()> { fn read_connection(
wrapped_stream: Arc<RwLock<I2pStream>>,
state: Arc<CommState>,
) -> JoinHandle<()> {
tokio::spawn(async move { tokio::spawn(async move {
let mut stream = wrapped_stream.write().await; let mut stream = wrapped_stream.write().await;
let peer: PeerId = stream.peer_addr().expect("Failed to get peer addr").into(); let peer: PeerId = stream.peer_addr().expect("Failed to get peer addr").into();
// All streams start with a \n byte which does not belong to the payload, take that from the stream. // All streams start with a \n byte which does not belong to the payload, take that from the stream.
if let Err(e) = stream.read(&mut [0; 1]) { if let Err(e) = stream.read(&mut [0; 1]) {
error!("Error while reading first byte of stream: {}", e); error!("Error while reading first byte of stream: {}", e);
return; return;
} }
let iterator = serde_json::Deserializer::from_reader(&mut *stream).into_iter::<serde_json::Value>(); let iterator = serde_json::Deserializer::from_reader(&mut *stream)
.into_iter::<serde_json::Value>();
for item in iterator { for item in iterator {
match item { match item {
Ok(value) => { Ok(value) => match serde_json::from_value::<Message>(value) {
match serde_json::from_value::<Message>(value) { Ok(message) => {
Ok(message) => { message_processor::handle(state.deref(), &peer, message);
message_processor::handle(state.deref(), &peer, message);
},
Err(e) => warn!("Deserialization failed: {:?}", e),
} }
Err(e) => warn!("Deserialization failed: {:?}", e),
}, },
Err(e) => { Err(e) => {
warn!("Deserialization failed: {:?}", e); warn!("Deserialization failed: {:?}", e);
@ -188,39 +198,52 @@ impl CommHandle {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use i2p::Session;
use i2p::net::I2pListener; use i2p::net::I2pListener;
use i2p::sam::StreamForward; use i2p::sam::StreamForward;
use i2p::sam_options::SAMOptions; use i2p::sam_options::SAMOptions;
use i2p::Session;
use crate::Config;
use crate::state::{State, CommState};
use crate::comm::{messages, Message}; use crate::comm::{messages, Message};
use crate::state::types::ElementId; use crate::state::types::ElementId;
use crate::state::{CommState, State};
use crate::Config;
use super::CommHandle; use super::CommHandle;
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
pub async fn msg() { pub async fn msg() {
let ch = CommHandle::new(CommState::new(State::new().await.unwrap()), &Config::default() ).unwrap(); let ch = CommHandle::new(
CommState::new(State::new().await.unwrap()),
&Config::default(),
)
.unwrap();
ch.run().await; ch.run().await;
println!("My address: {:?}", ch.i2p_b32_address()); println!("My address: {:?}", ch.i2p_b32_address());
ch.send( ch.send(
&ch.i2p_address().unwrap(), &ch.i2p_address().unwrap(),
Message::new(messages::MessageContent::Hello { peer_name: "a".to_string() }) Message::new(messages::MessageContent::Hello {
).await.expect("Could not send hello"); peer_name: "a".to_string(),
}),
)
.await
.expect("Could not send hello");
for i in 0..10 { for i in 0..10 {
let result = ch.send( let result = ch
&ch.i2p_address().unwrap(), .send(
Message::new(messages::MessageContent::CreateElement { id: ElementId::new(), content: crate::state::types::ElementContent::Text(format!("hello world no. {}", i)) }) &ch.i2p_address().unwrap(),
).await; Message::new(messages::MessageContent::CreateElement {
id: ElementId::new(),
content: crate::state::types::ElementContent::Text(format!(
"hello world no. {}",
i
)),
}),
)
.await;
tokio::time::sleep(Duration::from_millis(300)).await; tokio::time::sleep(Duration::from_millis(300)).await;
println!("Result of sending: {:?}", result); println!("Result of sending: {:?}", result);
} }
@ -232,11 +255,11 @@ mod tests {
pub fn from_privkey() { pub fn from_privkey() {
let privkey = "DPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gQz1KRvzfH0pI7uNWn8ZiRKQ812KjOrOMqnq7owjAjSBDPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gQz1KRvzfH0pI7uNWn8ZiRKQ812KjOrOMqnq7owjAjSBDPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gVKUHq0huLwfh0u06PlujRXTgcUJw9pg4Vkh-e0CGQFL6yn2FxCUIvaryyFt3-8mwO1OTkQyB7u1rnO9FpLlKeT9FPSkwmaxZmwQ1kvsuTTIp5ntxQZ1XMCDm2qhRWdcEsYxTKLJIMYxN1Ujk9Y7SqNYORmxrwQWC4ENGnt~VyvbAAAAfAabqgU0GhMWN2syDQ5sYZ61WXDqC4esasxwyLvJ-ES7~k40Uq9htc8t16-RXEq0Q17C499WxW6~GQRcXbgBNd0bMdV-46RsFo1jNgfB6H4nkuTrQXMqXB6s2Fhx2gwcHRk3Lt5DE4N0mvHG8Po974tJWr1hIRiSxQUtSj5kcZOOT~EKWMoCA7qDgZORZAnJavaRr0S-PiPQwAw8HOekdw50CyOByxXEfLBAi-Kz1nhdNvMHIrtcBZ~RpsxOK63O633e0PeYwrOOG7AFVLh7SzdwVvI1-KUe7y2ADBcoHuJRMwk5MEV-BATEfhWA2SzWw1qFRzJyb-pGbgGCJQOoc1YcP8jikBJhtuRbD5K-wK5MXoHL"; let privkey = "DPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gQz1KRvzfH0pI7uNWn8ZiRKQ812KjOrOMqnq7owjAjSBDPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gQz1KRvzfH0pI7uNWn8ZiRKQ812KjOrOMqnq7owjAjSBDPUpG~N8fSkju41afxmJEpDzXYqM6s4yqerujCMCNIEM9Skb83x9KSO7jVp~GYkSkPNdiozqzjKp6u6MIwI0gVKUHq0huLwfh0u06PlujRXTgcUJw9pg4Vkh-e0CGQFL6yn2FxCUIvaryyFt3-8mwO1OTkQyB7u1rnO9FpLlKeT9FPSkwmaxZmwQ1kvsuTTIp5ntxQZ1XMCDm2qhRWdcEsYxTKLJIMYxN1Ujk9Y7SqNYORmxrwQWC4ENGnt~VyvbAAAAfAabqgU0GhMWN2syDQ5sYZ61WXDqC4esasxwyLvJ-ES7~k40Uq9htc8t16-RXEq0Q17C499WxW6~GQRcXbgBNd0bMdV-46RsFo1jNgfB6H4nkuTrQXMqXB6s2Fhx2gwcHRk3Lt5DE4N0mvHG8Po974tJWr1hIRiSxQUtSj5kcZOOT~EKWMoCA7qDgZORZAnJavaRr0S-PiPQwAw8HOekdw50CyOByxXEfLBAi-Kz1nhdNvMHIrtcBZ~RpsxOK63O633e0PeYwrOOG7AFVLh7SzdwVvI1-KUe7y2ADBcoHuJRMwk5MEV-BATEfhWA2SzWw1qFRzJyb-pGbgGCJQOoc1YcP8jikBJhtuRbD5K-wK5MXoHL";
let _ = I2pListener { let _ = I2pListener {
forward: StreamForward::with_session(&Session::from_destination( forward: StreamForward::with_session(
i2p::sam::DEFAULT_API, &Session::from_destination(i2p::sam::DEFAULT_API, &privkey, SAMOptions::default())
&privkey, .expect("Failed to create session for listener2"),
SAMOptions::default()).expect("Failed to create session for listener2") )
).expect("Failed to create StreamForward for listener2") .expect("Failed to create StreamForward for listener2"),
}; };
} }
} }

View file

@ -1,11 +1,10 @@
use anyhow::bail; use anyhow::bail;
use i2p::net::I2pSocketAddr; use i2p::net::I2pSocketAddr;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use crate::state::types::PeerId; use crate::state::types::PeerId;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Peer { pub struct Peer {
id: PeerId, id: PeerId,
@ -15,7 +14,11 @@ pub struct Peer {
impl Peer { impl Peer {
pub fn new(id: PeerId, name: String) -> Self { pub fn new(id: PeerId, name: String) -> Self {
Peer {id: id, name: name, family: vec![]} Peer {
id: id,
name: name,
family: vec![],
}
} }
pub fn addr(&self) -> I2pSocketAddr { pub fn addr(&self) -> I2pSocketAddr {
@ -48,4 +51,4 @@ impl TryFrom<String> for Peer {
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str()) Self::try_from(value.as_str())
} }
} }

View file

@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config { pub struct Config {
@ -9,7 +9,11 @@ pub struct Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { i2p_private_key: None, api_config: Default::default(), jwt_secret: "insecuresecret".to_string() } Config {
i2p_private_key: None,
api_config: Default::default(),
jwt_secret: "insecuresecret".to_string(),
}
} }
} }
@ -22,6 +26,10 @@ pub struct ApiConfig {
impl Default for ApiConfig { impl Default for ApiConfig {
fn default() -> Self { fn default() -> Self {
ApiConfig { ip: None, port: None, version: None } ApiConfig {
ip: None,
port: None,
version: None,
}
} }
} }

View file

@ -5,8 +5,11 @@ use api::{Api, ApiBuilder};
use comm::{CommHandle, Peer}; use comm::{CommHandle, Peer};
use config::Config; use config::Config;
use i2p::net::I2pSocketAddr; use i2p::net::I2pSocketAddr;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use state::{State, types::{MessageId, PeerId}, CommState, ApiState}; use state::{
types::{MessageId, PeerId},
ApiState, CommState, State,
};
pub mod api; pub mod api;
pub mod comm; pub mod comm;
@ -18,7 +21,6 @@ pub struct MessageRelations {
pub parents: Vec<MessageId>, pub parents: Vec<MessageId>,
} }
pub struct Ubisync { pub struct Ubisync {
comm_handle: Arc<CommHandle>, comm_handle: Arc<CommHandle>,
state_handle: Arc<State>, state_handle: Arc<State>,
@ -31,13 +33,17 @@ impl Ubisync {
let comm_handle = Arc::new(CommHandle::new(CommState::new(state.clone()), config)?); let comm_handle = Arc::new(CommHandle::new(CommState::new(state.clone()), config)?);
state.set_comm_handle(comm_handle.clone()); state.set_comm_handle(comm_handle.clone());
let api = Arc::new(ApiBuilder::from(config.api_config.clone()).build(ApiState::new(state.clone(), &config.jwt_secret)).await); let api = Arc::new(
ApiBuilder::from(config.api_config.clone())
.build(ApiState::new(state.clone(), &config.jwt_secret))
.await,
);
comm_handle.run().await; comm_handle.run().await;
Ok(Ubisync { Ok(Ubisync {
comm_handle: comm_handle, comm_handle: comm_handle,
state_handle: state, state_handle: state,
api: api api: api,
}) })
} }
@ -45,7 +51,7 @@ impl Ubisync {
self.api.clone() self.api.clone()
} }
pub fn add_peer(&self, p: impl TryInto<Peer, Error=anyhow::Error>) -> anyhow::Result<()> { pub fn add_peer(&self, p: impl TryInto<Peer, Error = anyhow::Error>) -> anyhow::Result<()> {
match p.try_into() { match p.try_into() {
Ok(peer) => self.state_handle.set_peer(&peer), Ok(peer) => self.state_handle.set_peer(&peer),
Err(e) => bail!(e), Err(e) => bail!(e),
@ -66,7 +72,6 @@ impl Ubisync {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]

View file

@ -2,14 +2,19 @@ use std::sync::Arc;
use chrono::Utc; use chrono::Utc;
use cozo::DbInstance; use cozo::DbInstance;
use jsonwebtoken::{EncodingKey, DecodingKey, Validation}; use jsonwebtoken::{DecodingKey, EncodingKey, Validation};
use tracing::debug; use tracing::debug;
use crate::{state::{types::ElementId, queries}, comm::messages::MessageContent, api::{AppDescription, AppId}}; use crate::{
api::v0::app::{AppDescription, AppId},
use super::{State, types::{ElementContent, Element}}; comm::messages::MessageContent,
state::{queries, types::ElementId},
};
use super::{
types::{Element, ElementContent},
State,
};
pub struct ApiState { pub struct ApiState {
state: Arc<State>, state: Arc<State>,
@ -33,7 +38,13 @@ impl ApiState {
pub fn add_app(&self, description: &AppDescription) -> anyhow::Result<AppId> { pub fn add_app(&self, description: &AppDescription) -> anyhow::Result<AppId> {
let id = AppId::new(); let id = AppId::new();
let last_access = Utc::now(); let last_access = Utc::now();
queries::apps::add(self.db(), &id, &last_access, &description.name, &description.desc_text)?; queries::apps::add(
self.db(),
&id,
&last_access,
&description.name,
&description.desc_text,
)?;
debug!("Successfully added app"); debug!("Successfully added app");
Ok(id) Ok(id)
@ -48,11 +59,18 @@ impl ApiState {
queries::elements::add(self.db(), &id, &content, None, true)?; queries::elements::add(self.db(), &id, &content, None, true)?;
debug!("Added element {{{}}}", &id.to_string()); debug!("Added element {{{}}}", &id.to_string());
self.state.send_to_peers(MessageContent::CreateElement { id: id.clone(), content: content.clone() }); self.state.send_to_peers(MessageContent::CreateElement {
id: id.clone(),
content: content.clone(),
});
Ok(id) Ok(id)
} }
pub fn write_element_content(&self, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> { pub fn write_element_content(
&self,
id: &ElementId,
content: &ElementContent,
) -> anyhow::Result<()> {
queries::elements::set_content(self.db(), id, content)?; queries::elements::set_content(self.db(), id, content)?;
queries::elements::set_local_changes(self.db(), id, true)?; queries::elements::set_local_changes(self.db(), id, true)?;
debug!("Wrote element content {{{}}}", &id.to_string()); debug!("Wrote element content {{{}}}", &id.to_string());
@ -71,7 +89,6 @@ impl ApiState {
self.state.get_element(id) self.state.get_element(id)
} }
fn db(&self) -> &DbInstance { fn db(&self) -> &DbInstance {
&self.state.db &self.state.db
} }
@ -93,13 +110,18 @@ impl ApiState {
mod tests { mod tests {
use super::ApiState; use super::ApiState;
use crate::state::{types::ElementContent, State}; use crate::state::{types::ElementContent, State};
#[tokio::test] #[tokio::test]
#[serial_test::serial] #[serial_test::serial]
async fn test_element_create() { async fn test_element_create() {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = ApiState::new(State::new().await.unwrap(), "abcdabcdabcdabcdabcdabcdabcdabcd"); let state = ApiState::new(
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); State::new().await.unwrap(),
"abcdabcdabcdabcdabcdabcdabcdabcd",
);
let id = state
.create_element(&ElementContent::Text("Test-text".to_string()))
.unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();
assert_eq!( assert_eq!(
ElementContent::Text("Test-text".to_string()), ElementContent::Text("Test-text".to_string()),
@ -111,9 +133,16 @@ mod tests {
#[serial_test::serial] #[serial_test::serial]
async fn test_element_write() { async fn test_element_write() {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = ApiState::new(State::new().await.unwrap(), "abcdabcdabcdabcdabcdabcdabcdabcd"); let state = ApiState::new(
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); State::new().await.unwrap(),
state.write_element_content(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap(); "abcdabcdabcdabcdabcdabcdabcdabcd",
);
let id = state
.create_element(&ElementContent::Text("Test-text".to_string()))
.unwrap();
state
.write_element_content(&id, &ElementContent::Text("Test-text 2".to_string()))
.unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();
assert_eq!( assert_eq!(
ElementContent::Text("Test-text 2".to_string()), ElementContent::Text("Test-text 2".to_string()),

View file

@ -3,29 +3,47 @@ use std::sync::Arc;
use cozo::DbInstance; use cozo::DbInstance;
use tracing::debug; use tracing::debug;
use crate::{state::queries, comm::Peer}; use crate::{comm::Peer, state::queries};
use super::{State, types::{MessageId, ElementContent, ElementId, PeerId, Element}};
use super::{
types::{Element, ElementContent, ElementId, MessageId, PeerId},
State,
};
//TODO: Notify API about changes //TODO: Notify API about changes
pub struct CommState { pub struct CommState {
state: Arc<State> state: Arc<State>,
} }
impl CommState { impl CommState {
pub fn new(state: Arc<State>) -> Self { pub fn new(state: Arc<State>) -> Self {
CommState { state: state } CommState { state: state }
} }
pub fn add_received_element(&self, id: &ElementId, content: &ElementContent, latest_message: &MessageId) -> anyhow::Result<()> { pub fn add_received_element(
queries::elements::add(self.db(), &id, &content, Some(latest_message.to_owned()), false)?; &self,
id: &ElementId,
content: &ElementContent,
latest_message: &MessageId,
) -> anyhow::Result<()> {
queries::elements::add(
self.db(),
&id,
&content,
Some(latest_message.to_owned()),
false,
)?;
debug!("Added element {{{}}}", &id.to_string()); debug!("Added element {{{}}}", &id.to_string());
Ok(()) Ok(())
} }
pub fn update_element_content(&self, id: &ElementId, content: &ElementContent, latest_message: &MessageId) -> anyhow::Result<()> { pub fn update_element_content(
&self,
id: &ElementId,
content: &ElementContent,
latest_message: &MessageId,
) -> anyhow::Result<()> {
//TODO: resolve potential conflicts with local changes //TODO: resolve potential conflicts with local changes
queries::elements::set_content(self.db(), id, content)?; queries::elements::set_content(self.db(), id, content)?;
queries::elements::set_latest_message(self.db(), id, Some(latest_message.to_owned()))?; queries::elements::set_latest_message(self.db(), id, Some(latest_message.to_owned()))?;
@ -48,7 +66,7 @@ impl CommState {
pub fn set_peer(&self, id: &PeerId, name: &str) -> anyhow::Result<()> { pub fn set_peer(&self, id: &PeerId, name: &str) -> anyhow::Result<()> {
queries::peers::put(self.db(), id, name)?; queries::peers::put(self.db(), id, name)?;
debug!("Set peer {{{}}}", id.to_string()); debug!("Set peer {{{}}}", id.to_string());
Ok(()) Ok(())
} }
@ -56,7 +74,6 @@ impl CommState {
self.state.get_peers() self.state.get_peers()
} }
fn db(&self) -> &DbInstance { fn db(&self) -> &DbInstance {
&self.state.db &self.state.db
} }
@ -65,15 +82,24 @@ impl CommState {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::CommState; use super::CommState;
use crate::state::{types::{ElementContent, ElementId, MessageId}, State}; use crate::state::{
types::{ElementContent, ElementId, MessageId},
State,
};
#[tokio::test] #[tokio::test]
#[serial_test::serial] #[serial_test::serial]
async fn test_element_add() { async fn test_element_add() {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = CommState::new(State::new().await.unwrap()); let state = CommState::new(State::new().await.unwrap());
let id = ElementId::new(); let id = ElementId::new();
state.add_received_element(&id, &ElementContent::Text("Test-text".to_string()), &MessageId::new()).unwrap(); state
.add_received_element(
&id,
&ElementContent::Text("Test-text".to_string()),
&MessageId::new(),
)
.unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();
assert_eq!( assert_eq!(
ElementContent::Text("Test-text".to_string()), ElementContent::Text("Test-text".to_string()),
@ -87,8 +113,20 @@ mod tests {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = CommState::new(State::new().await.unwrap()); let state = CommState::new(State::new().await.unwrap());
let id = ElementId::new(); let id = ElementId::new();
state.add_received_element(&id, &ElementContent::Text("Test-text".to_string()), &MessageId::new()).unwrap(); state
state.update_element_content(&id,&ElementContent::Text("Test-text 2".to_string()), &MessageId::new()).unwrap(); .add_received_element(
&id,
&ElementContent::Text("Test-text".to_string()),
&MessageId::new(),
)
.unwrap();
state
.update_element_content(
&id,
&ElementContent::Text("Test-text 2".to_string()),
&MessageId::new(),
)
.unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();
assert_eq!( assert_eq!(
ElementContent::Text("Test-text 2".to_string()), ElementContent::Text("Test-text 2".to_string()),

View file

@ -2,11 +2,14 @@ use std::sync::{Arc, RwLock};
use anyhow::Error; use anyhow::Error;
use cozo::DbInstance; use cozo::DbInstance;
use tracing::{error, debug}; use tracing::{debug, error};
use crate::comm::{Peer, CommHandle, messages::{Message, MessageContent}}; use crate::comm::{
messages::{Message, MessageContent},
CommHandle, Peer,
};
use self::types::{ElementContent, ElementId, Element, Tag}; use self::types::{Element, ElementContent, ElementId, Tag};
pub mod types; pub mod types;
@ -29,28 +32,48 @@ impl State {
match db { match db {
Ok(d) => { Ok(d) => {
schema::add_schema(&d)?; schema::add_schema(&d)?;
Ok(Arc::new(State {db: d, comm_handle: RwLock::new(None)})) Ok(Arc::new(State {
}, db: d,
comm_handle: RwLock::new(None),
}))
}
Err(e) => Err(Error::msg(format!("{:?}", e))), Err(e) => Err(Error::msg(format!("{:?}", e))),
} }
} }
pub fn set_comm_handle(&self, handle: Arc<CommHandle>) { pub fn set_comm_handle(&self, handle: Arc<CommHandle>) {
*self.comm_handle.write().as_deref_mut().expect("Could not set state's CommHandle") = Some(handle); *self
.comm_handle
.write()
.as_deref_mut()
.expect("Could not set state's CommHandle") = Some(handle);
} }
pub fn set_element_content(&self, element_id: &ElementId, content: &ElementContent) -> anyhow::Result<()> { pub fn set_element_content(
&self,
element_id: &ElementId,
content: &ElementContent,
) -> anyhow::Result<()> {
let res = queries::elements::set_content(&self.db, element_id, content); let res = queries::elements::set_content(&self.db, element_id, content);
debug!("Set content of element with id {:?}: {:?}", element_id, self.get_element(element_id)); debug!(
"Set content of element with id {:?}: {:?}",
self.send_to_peers(MessageContent::SetElement { id: element_id.clone(), content: content.clone() }); element_id,
self.get_element(element_id)
);
self.send_to_peers(MessageContent::SetElement {
id: element_id.clone(),
content: content.clone(),
});
res res
} }
pub fn remove_element(&self, element_id: &ElementId) -> anyhow::Result<()> { pub fn remove_element(&self, element_id: &ElementId) -> anyhow::Result<()> {
let res = queries::elements::remove(&self.db, element_id); let res = queries::elements::remove(&self.db, element_id);
self.send_to_peers(MessageContent::RemoveElement { id: element_id.clone() }); self.send_to_peers(MessageContent::RemoveElement {
id: element_id.clone(),
});
res res
} }
@ -60,8 +83,11 @@ impl State {
pub fn get_elements_by_tag(&self, tag: &Tag) -> Vec<ElementId> { pub fn get_elements_by_tag(&self, tag: &Tag) -> Vec<ElementId> {
queries::elements::get_by_tag(&self.db, tag) queries::elements::get_by_tag(&self.db, tag)
.map_err(|e| {error!("{}", e); e}) .map_err(|e| {
.unwrap_or(vec![]) error!("{}", e);
e
})
.unwrap_or(vec![])
} }
pub fn set_peer(&self, peer: &Peer) -> anyhow::Result<()> { pub fn set_peer(&self, peer: &Peer) -> anyhow::Result<()> {
@ -72,8 +98,6 @@ impl State {
queries::peers::get(&self.db) queries::peers::get(&self.db)
} }
fn send_to_peers(&self, ct: MessageContent) { fn send_to_peers(&self, ct: MessageContent) {
match self.comm_handle.read() { match self.comm_handle.read() {
Ok(opt) => { Ok(opt) => {
@ -83,7 +107,7 @@ impl State {
let _ = arc.broadcast(Message::new(ct)).await; let _ = arc.broadcast(Message::new(ct)).await;
}); });
} }
}, }
Err(e) => debug!("{}", e), Err(e) => debug!("{}", e),
} }
} }

View file

@ -2,35 +2,57 @@ use std::collections::BTreeMap;
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use cozo::{DbInstance, DataValue, Num}; use cozo::{DataValue, DbInstance, Num};
use crate::{run_query, api::AppId}; use crate::{api::v0::app::AppId, run_query};
pub fn add(
db: &DbInstance,
pub fn add(db: &DbInstance, id: &AppId, last_access: &DateTime<Utc>, name: &str, description: &str) -> anyhow::Result<()> { id: &AppId,
last_access: &DateTime<Utc>,
name: &str,
description: &str,
) -> anyhow::Result<()> {
let params = vec![ let params = vec![
("id", DataValue::Str(serde_json::to_string(&id).unwrap().into())), (
("last_access", DataValue::Num(Num::Int(last_access.timestamp()))), "id",
DataValue::Str(serde_json::to_string(&id).unwrap().into()),
),
(
"last_access",
DataValue::Num(Num::Int(last_access.timestamp())),
),
("name", DataValue::Str(name.into())), ("name", DataValue::Str(name.into())),
("description", DataValue::Str(description.into())), ("description", DataValue::Str(description.into())),
]; ];
match run_query!(&db, ":insert apps {id => last_access, name, description}", params, cozo::ScriptMutability::Mutable) { match run_query!(
&db,
":insert apps {id => last_access, name, description}",
params,
cozo::ScriptMutability::Mutable
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(report) => bail!(report) Err(report) => bail!(report),
} }
} }
pub fn exists(db: &DbInstance, id: &AppId) -> anyhow::Result<bool> { pub fn exists(db: &DbInstance, id: &AppId) -> anyhow::Result<bool> {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
params.insert("id".to_string(), DataValue::Str(serde_json::to_string(&id)?.into())); params.insert(
"id".to_string(),
DataValue::Str(serde_json::to_string(&id)?.into()),
);
let result = db.run_script("?[name] := *apps[$id, last_access, name, description]", params, cozo::ScriptMutability::Immutable); let result = db.run_script(
"?[name] := *apps[$id, last_access, name, description]",
params,
cozo::ScriptMutability::Immutable,
);
if let Ok(rows) = result { if let Ok(rows) = result {
return Ok(rows.rows.len() == 1); return Ok(rows.rows.len() == 1);
} }
Err(Error::msg("Could not check whether app is registered")) Err(Error::msg("Could not check whether app is registered"))
} }

View file

@ -1,46 +1,96 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use anyhow::{Error, bail}; use anyhow::{bail, Error};
use cozo::{DbInstance, DataValue, JsonData, ScriptMutability}; use cozo::{DataValue, DbInstance, JsonData, ScriptMutability};
use serde_json::Value; use serde_json::Value;
use tracing::{error, debug}; use tracing::{debug, error};
use crate::{state::{ElementContent, ElementId, Element, types::{Tag, MessageId}}, run_query}; use crate::{
run_query,
state::{
types::{MessageId, Tag},
Element, ElementContent, ElementId,
},
};
pub fn add(db: &DbInstance, id: &ElementId, content: &ElementContent, latest_message: Option<MessageId>, local_changes: bool) -> anyhow::Result<()> { pub fn add(
db: &DbInstance,
id: &ElementId,
content: &ElementContent,
latest_message: Option<MessageId>,
local_changes: bool,
) -> anyhow::Result<()> {
let params = vec![ let params = vec![
("id", DataValue::Str(serde_json::to_string(&id)?.into())), ("id", DataValue::Str(serde_json::to_string(&id)?.into())),
("content", DataValue::Json(JsonData(serde_json::to_value(content)?))), (
("latest_message", match latest_message { "content",
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()), DataValue::Json(JsonData(serde_json::to_value(content)?)),
None => DataValue::Null, ),
}), (
"latest_message",
match latest_message {
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()),
None => DataValue::Null,
},
),
("local_changes", DataValue::Bool(local_changes)), ("local_changes", DataValue::Bool(local_changes)),
]; ];
match run_query!(&db, ":insert elements {id => content, latest_message, local_changes}", params, ScriptMutability::Mutable) { match run_query!(
&db,
":insert elements {id => content, latest_message, local_changes}",
params,
ScriptMutability::Mutable
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(report) => bail!(report), Err(report) => bail!(report),
} }
} }
pub fn set_content(db: &DbInstance, id: &ElementId, content: &ElementContent) -> anyhow::Result<()> { pub fn set_content(
set_property(db, id, "content", DataValue::Json(JsonData(serde_json::to_value(content)?))) db: &DbInstance,
id: &ElementId,
content: &ElementContent,
) -> anyhow::Result<()> {
set_property(
db,
id,
"content",
DataValue::Json(JsonData(serde_json::to_value(content)?)),
)
} }
pub fn set_latest_message(db: &DbInstance, id: &ElementId, latest_message: Option<MessageId>) -> anyhow::Result<()> { pub fn set_latest_message(
set_property(db, id, "latest_message", match latest_message { db: &DbInstance,
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()), id: &ElementId,
None => DataValue::Null, latest_message: Option<MessageId>,
}) ) -> anyhow::Result<()> {
set_property(
db,
id,
"latest_message",
match latest_message {
Some(m) => DataValue::Str(serde_json::to_string(&m)?.into()),
None => DataValue::Null,
},
)
} }
pub fn set_local_changes(db: &DbInstance, id: &ElementId, local_changes: bool) -> anyhow::Result<()> { pub fn set_local_changes(
db: &DbInstance,
id: &ElementId,
local_changes: bool,
) -> anyhow::Result<()> {
set_property(db, id, "local_changes", DataValue::Bool(local_changes)) set_property(db, id, "local_changes", DataValue::Bool(local_changes))
} }
pub fn remove(db: &DbInstance, id: &ElementId) -> anyhow::Result<()> { pub fn remove(db: &DbInstance, id: &ElementId) -> anyhow::Result<()> {
match run_query!(&db, ":delete elements {id}", vec![("id", DataValue::Str(serde_json::to_string(&id)?.into()))], cozo::ScriptMutability::Mutable) { match run_query!(
&db,
":delete elements {id}",
vec![("id", DataValue::Str(serde_json::to_string(&id)?.into()))],
cozo::ScriptMutability::Mutable
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(report) => bail!(report), Err(report) => bail!(report),
} }
@ -48,7 +98,10 @@ pub fn remove(db: &DbInstance, id: &ElementId) -> anyhow::Result<()> {
pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> { pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
params.insert("id".to_string(), DataValue::Str(serde_json::to_string(&id)?.into())); params.insert(
"id".to_string(),
DataValue::Str(serde_json::to_string(&id)?.into()),
);
let result = db.run_script(" let result = db.run_script("
?[content, latest_message, local_changes] := *elements[$id, content, latest_message, local_changes] ?[content, latest_message, local_changes] := *elements[$id, content, latest_message, local_changes]
@ -57,7 +110,9 @@ pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
Ok(val) => { Ok(val) => {
if let Some(firstrow) = val.rows.first() { if let Some(firstrow) = val.rows.first() {
debug!("db result: {:?}", &firstrow.as_slice()); debug!("db result: {:?}", &firstrow.as_slice());
if let [ DataValue::Json(JsonData(content)), latest_message, DataValue::Bool(local_changes) ] = firstrow.as_slice() { if let [DataValue::Json(JsonData(content)), latest_message, DataValue::Bool(local_changes)] =
firstrow.as_slice()
{
return Ok(Element::from(( return Ok(Element::from((
id.to_owned(), id.to_owned(),
serde_json::from_value(content.to_owned())?, serde_json::from_value(content.to_owned())?,
@ -65,56 +120,71 @@ pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
DataValue::Str(s) => Some(serde_json::from_str(s)?), DataValue::Str(s) => Some(serde_json::from_str(s)?),
_ => None, _ => None,
}, },
local_changes.to_owned() local_changes.to_owned(),
))); )));
} }
return Err(Error::msg("Could not parse db result as Element")); return Err(Error::msg("Could not parse db result as Element"));
} else {
return Err(Error::msg("No rows returned for element query"));
} }
else { }
return Err(Error::msg("No rows returned for element query"))
}
},
Err(report) => bail!(report), Err(report) => bail!(report),
} }
} }
pub fn get_by_tag(db: &DbInstance, tag: &Tag) -> anyhow::Result<Vec<ElementId>> { pub fn get_by_tag(db: &DbInstance, tag: &Tag) -> anyhow::Result<Vec<ElementId>> {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
params.insert("tag".to_string(), DataValue::Str(serde_json::to_string(tag)?.into())); params.insert(
"tag".to_string(),
DataValue::Str(serde_json::to_string(tag)?.into()),
);
let result = db.run_script(" let result = db.run_script(
"
?[element] := *tags[$tag, element] ?[element] := *tags[$tag, element]
", params, cozo::ScriptMutability::Immutable); ",
params,
cozo::ScriptMutability::Immutable,
);
match result { match result {
Ok(named_rows) => { Ok(named_rows) => {
let mut element_ids = vec![]; let mut element_ids = vec![];
for row in named_rows.rows { for row in named_rows.rows {
if let [ DataValue::Json(JsonData(Value::String(element_id)))] = row.as_slice() { if let [DataValue::Json(JsonData(Value::String(element_id)))] = row.as_slice() {
match serde_json::from_str(&element_id) { match serde_json::from_str(&element_id) {
Ok(id) => element_ids.push(id), Ok(id) => element_ids.push(id),
Err(e) => { Err(e) => {
error!("Error parsing element id {}: {}", element_id, e); error!("Error parsing element id {}: {}", element_id, e);
continue continue;
} }
} }
} }
} }
Ok(element_ids) Ok(element_ids)
}, }
Err(report) => bail!(report), Err(report) => bail!(report),
} }
} }
fn set_property(
fn set_property(db: &DbInstance, id: &ElementId, key: &str, value: DataValue) -> anyhow::Result<()> { db: &DbInstance,
id: &ElementId,
key: &str,
value: DataValue,
) -> anyhow::Result<()> {
let params = vec![ let params = vec![
("id", DataValue::Str(serde_json::to_string(id)?.into())), ("id", DataValue::Str(serde_json::to_string(id)?.into())),
(key, value) (key, value),
]; ];
match run_query!(&db, format!(":update elements {{id => {key}}}"), params, ScriptMutability::Mutable) { match run_query!(
&db,
format!(":update elements {{id => {key}}}"),
params,
ScriptMutability::Mutable
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(report) => bail!(report), Err(report) => bail!(report),
} }
} }

View file

@ -4,47 +4,47 @@ pub mod peers;
#[macro_export] #[macro_export]
macro_rules! build_query { macro_rules! build_query {
($payload:expr, $params:expr) => { ($payload:expr, $params:expr) => {{
{ use cozo::DataValue;
use cozo::DataValue; use std::collections::BTreeMap;
use std::collections::BTreeMap; // Build parameters map
// Build parameters map let mut params_map: BTreeMap<String, DataValue> = Default::default();
let mut params_map: BTreeMap<String, DataValue> = Default::default(); let mut parameters_init = String::new();
let mut parameters_init = String::new();
if $params.len() > 0 { if $params.len() > 0 {
for (name, value) in $params { for (name, value) in $params {
let _: &str = name; // only for type annotation let _: &str = name; // only for type annotation
params_map.insert(name.to_string(), value); params_map.insert(name.to_string(), value);
}
// First line: Initialize parameters, make them available in CozoScript
use itertools::Itertools;
parameters_init += "?[";
parameters_init += &params_map.iter().map(|(name, _)| name).format(", ").to_string();
parameters_init += "] <- [[";
parameters_init += &params_map.iter().map(|(name, _)| format!("${}", name)).format(", ").to_string();
parameters_init += "]]";
} }
// Return query string and parameters map // First line: Initialize parameters, make them available in CozoScript
( use itertools::Itertools;
format!("{}\n\n{}", parameters_init, $payload), parameters_init += "?[";
params_map parameters_init += &params_map
) .iter()
.map(|(name, _)| name)
.format(", ")
.to_string();
parameters_init += "] <- [[";
parameters_init += &params_map
.iter()
.map(|(name, _)| format!("${}", name))
.format(", ")
.to_string();
parameters_init += "]]";
} }
};
}
// Return query string and parameters map
(format!("{}\n\n{}", parameters_init, $payload), params_map)
}};
}
use build_query; use build_query;
#[macro_export] #[macro_export]
macro_rules! run_query { macro_rules! run_query {
($db:expr, $payload:expr, $params:expr, $mutability:expr) => { ($db:expr, $payload:expr, $params:expr, $mutability:expr) => {{
{ let (query, parameters) = crate::state::queries::build_query!($payload, $params);
let (query, parameters) = crate::state::queries::build_query!($payload, $params); $db.run_script(query.as_str(), parameters, $mutability)
$db.run_script(query.as_str(), parameters, $mutability) }};
}
};
} }

View file

@ -1,47 +1,49 @@
use anyhow::Error; use anyhow::Error;
use cozo::{DbInstance, DataValue, ScriptMutability}; use cozo::{DataValue, DbInstance, ScriptMutability};
use crate::{state::types::PeerId, comm::Peer, run_query};
use crate::{comm::Peer, run_query, state::types::PeerId};
pub fn put(db: &DbInstance, id: &PeerId, name: &str) -> anyhow::Result<()> { pub fn put(db: &DbInstance, id: &PeerId, name: &str) -> anyhow::Result<()> {
let params = vec![ let params = vec![
("id", DataValue::Str(serde_json::to_string(id)?.into())), ("id", DataValue::Str(serde_json::to_string(id)?.into())),
("name", DataValue::Str(serde_json::to_string(name)?.into())) ("name", DataValue::Str(serde_json::to_string(name)?.into())),
]; ];
match run_query!(&db, ":put peers {id => name}", params, ScriptMutability::Mutable) { match run_query!(
&db,
":put peers {id => name}",
params,
ScriptMutability::Mutable
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(report) => Err(Error::msg(format!("Query failed: {}", report))), Err(report) => Err(Error::msg(format!("Query failed: {}", report))),
} }
} }
pub fn get(db: &DbInstance) -> anyhow::Result<Vec<Peer>> { pub fn get(db: &DbInstance) -> anyhow::Result<Vec<Peer>> {
let result = db.run_script(" let result = db.run_script(
"
?[id, name] := *peers{id, name} ?[id, name] := *peers{id, name}
", Default::default(), cozo::ScriptMutability::Immutable); ",
Default::default(),
cozo::ScriptMutability::Immutable,
);
match result { match result {
Ok(rows) => { Ok(rows) => Ok(rows
Ok( .rows
rows.rows.into_iter().map( .into_iter()
|row| { .map(|row| match row.as_slice() {
match row.as_slice() { [DataValue::Str(id_string), DataValue::Str(name_string)] => {
[DataValue::Str(id_string), DataValue::Str(name_string)] => { if let Ok(id) = serde_json::from_str(&id_string) {
if let Ok(id) = serde_json::from_str(&id_string) { Some(Peer::new(id, name_string.as_str().to_string()))
Some(Peer::new(id, name_string.as_str().to_string())) } else {
} None
else {
None
}},
_ => None
}
} }
) }
.flatten() _ => None,
.collect() })
) .flatten()
}, .collect()),
Err(report) => Err(Error::msg(format!("Query failed: {}", report))), Err(report) => Err(Error::msg(format!("Query failed: {}", report))),
} }
} }

View file

@ -3,11 +3,10 @@ use std::collections::BTreeMap;
use anyhow::Error; use anyhow::Error;
use cozo::DbInstance; use cozo::DbInstance;
pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> { pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> {
let params = BTreeMap::new(); let params = BTreeMap::new();
match db.run_script(" match db.run_script(
"
{:create apps { {:create apps {
id: String, id: String,
=> =>
@ -31,8 +30,11 @@ pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> {
tag: String, tag: String,
element: String, element: String,
}} }}
", params, cozo::ScriptMutability::Mutable) { ",
params,
cozo::ScriptMutability::Mutable,
) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(Error::msg(format!("Failed to set up schema: {}", e))), Err(e) => Err(Error::msg(format!("Failed to set up schema: {}", e))),
} }
} }

View file

@ -1,8 +1,6 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use super::{ElementId, ElementContent, MessageId};
use super::{ElementContent, ElementId, MessageId};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Element { pub struct Element {
@ -15,14 +13,24 @@ pub struct Element {
impl From<(ElementId, ElementContent, Option<MessageId>, bool)> for Element { impl From<(ElementId, ElementContent, Option<MessageId>, bool)> for Element {
fn from(value: (ElementId, ElementContent, Option<MessageId>, bool)) -> Self { fn from(value: (ElementId, ElementContent, Option<MessageId>, bool)) -> Self {
Element { id: value.0, content: value.1, latest_message: value.2, local_changes: value.3 } Element {
id: value.0,
content: value.1,
latest_message: value.2,
local_changes: value.3,
}
} }
} }
impl Element { impl Element {
pub fn new(id: ElementId, content: ElementContent) -> Self { pub fn new(id: ElementId, content: ElementContent) -> Self {
// A new element with no latest message must have local changes // A new element with no latest message must have local changes
Element { id: id, content: content, latest_message: None, local_changes: true } Element {
id: id,
content: content,
latest_message: None,
local_changes: true,
}
} }
pub fn id(&self) -> &ElementId { pub fn id(&self) -> &ElementId {
@ -31,4 +39,4 @@ impl Element {
pub fn content(&self) -> &ElementContent { pub fn content(&self) -> &ElementContent {
&self.content &self.content
} }
} }

View file

@ -1,7 +1,4 @@
use serde::{Deserialize, Serialize};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum ElementContent { pub enum ElementContent {
@ -13,7 +10,7 @@ impl TryFrom<ElementContent> for String {
fn try_from(value: ElementContent) -> Result<Self, Self::Error> { fn try_from(value: ElementContent) -> Result<Self, Self::Error> {
match serde_json::to_string(&value) { match serde_json::to_string(&value) {
Ok(s) => Ok(s), Ok(s) => Ok(s),
Err(e) => Err(e) Err(e) => Err(e),
} }
} }
} }
@ -23,7 +20,7 @@ impl TryFrom<&str> for ElementContent {
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
match serde_json::from_str(value) { match serde_json::from_str(value) {
Ok(ec) => Ok(ec), Ok(ec) => Ok(ec),
Err(e) => Err(e) Err(e) => Err(e),
} }
} }
} }

View file

@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -26,4 +26,4 @@ impl TryFrom<&str> for ElementId {
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
serde_json::from_str(value) serde_json::from_str(value)
} }
} }

View file

@ -1,10 +1,9 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MessageId(Uuid); pub struct MessageId(Uuid);
impl MessageId { impl MessageId {
pub fn new() -> Self { pub fn new() -> Self {
MessageId(Uuid::new_v4()) MessageId(Uuid::new_v4())
@ -28,4 +27,4 @@ impl TryFrom<&str> for MessageId {
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
serde_json::from_str(value) serde_json::from_str(value)
} }
} }

View file

@ -1,7 +1,6 @@
use anyhow::bail; use anyhow::bail;
use i2p::net::{I2pSocketAddr, ToI2pSocketAddrs}; use i2p::net::{I2pSocketAddr, ToI2pSocketAddrs};
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PeerId { pub struct PeerId {
@ -30,7 +29,7 @@ impl TryFrom<&str> for PeerId {
return Ok(PeerId { i2p_addr: addr }); return Ok(PeerId { i2p_addr: addr });
} }
return Err(anyhow::Error::msg("No valid I2P address found")); return Err(anyhow::Error::msg("No valid I2P address found"));
}, }
Err(e) => bail!(e), Err(e) => bail!(e),
} }
} }
@ -54,4 +53,4 @@ impl From<PeerId> for I2pSocketAddr {
fn from(value: PeerId) -> Self { fn from(value: PeerId) -> Self {
value.i2p_addr value.i2p_addr
} }
} }

View file

@ -1,7 +1,6 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tag { pub struct Tag {
tag: String, tag: String,
} }

View file

@ -1,40 +1,86 @@
use std::time::Duration; use std::time::Duration;
use tracing::{Level, debug}; use tracing::{debug, Level};
use ubisync::{Ubisync, config::Config, state::types::{ElementContent, Element, ElementId}, api::AppDescription}; use ubisync::{
api::v0::app::AppDescription,
config::Config,
state::types::{Element, ElementContent, ElementId},
Ubisync,
};
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn two_nodes_element_creation() { async fn two_nodes_element_creation() {
tracing_subscriber::fmt().pretty().with_max_level(Level::DEBUG).init(); tracing_subscriber::fmt()
.pretty()
.with_max_level(Level::DEBUG)
.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);
let ubi1 = Ubisync::new(&Config::default()).await.unwrap(); let ubi1 = Ubisync::new(&Config::default()).await.unwrap();
let ubi2 = Ubisync::new(&c2).await.unwrap(); let ubi2 = Ubisync::new(&c2).await.unwrap();
ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into()).unwrap(); ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into())
.unwrap();
let http_client = reqwest::Client::new(); let http_client = reqwest::Client::new();
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.unwrap(); let register_response = http_client
let jwt1 = register_response.text().await.expect("Couldn't fetch token from response"); .put("http://localhost:9981/v0/app/register")
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.unwrap(); .json(&AppDescription {
let jwt2 = register_response.text().await.expect("Couldn't fetch token from response"); name: "Test".to_string(),
desc_text: "desc".to_string(),
})
.send()
.await
.unwrap();
let jwt1 = register_response
.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
.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.put(&format!("http://localhost:9981/v0/element")).json(&test_element_content).header("Authorization", &format!("Bearer {}", &jwt1)).send().await.unwrap(); let put_resp = http_client
.put(&format!("http://localhost:9981/v0/element"))
.json(&test_element_content)
.header("Authorization", &format!("Bearer {}", &jwt1))
.send()
.await
.unwrap();
debug!("{:?}", &put_resp); debug!("{:?}", &put_resp);
let put_resp_text = put_resp.text().await.expect("No put response body"); let put_resp_text = put_resp.text().await.expect("No put response body");
debug!("{}", put_resp_text); debug!("{}", put_resp_text);
let id = serde_json::from_str::<ElementId>(&put_resp_text).expect("Could not deserialize ElementId"); 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(3000)).await;
let get_resp = http_client.get(&format!("http://localhost:9982/v0/element/{}", Into::<String>::into(&id))).header("Authorization", &format!("Bearer {}", &jwt2)).send().await.expect("Get request failed"); let get_resp = http_client
.get(&format!(
"http://localhost:9982/v0/element/{}",
Into::<String>::into(&id)
))
.header("Authorization", &format!("Bearer {}", &jwt2))
.send()
.await
.expect("Get request failed");
let get_resp_text = get_resp.text().await.expect("No get request body"); let get_resp_text = get_resp.text().await.expect("No get request body");
debug!("{}", get_resp_text); debug!("{}", get_resp_text);
let received_element = serde_json::from_str::<Element>(&get_resp_text).expect("Could not deserialize Element"); 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());
std::process::exit(0); std::process::exit(0);
} }