Simple pot-based access control
This commit is contained in:
parent
a768ce0f4e
commit
d1b12e1562
15 changed files with 340 additions and 64 deletions
|
@ -1,7 +1,7 @@
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::PotId;
|
use crate::types::{PotId, Pot};
|
||||||
|
|
||||||
use super::UbisyncRequest;
|
use super::UbisyncRequest;
|
||||||
|
|
||||||
|
@ -68,6 +68,27 @@ impl UbisyncRequest for AppSetDefaultPotRequest {
|
||||||
Method::POST
|
Method::POST
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self, _: Self::PathParameters) -> String {
|
||||||
|
"/app/pot/default".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AppGetDefaultPotRequest;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AppGetDefaultPotResponse {
|
||||||
|
pub pot: Pot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UbisyncRequest for AppGetDefaultPotRequest {
|
||||||
|
type PathParameters = ();
|
||||||
|
type Response = AppGetDefaultPotResponse;
|
||||||
|
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::GET
|
||||||
|
}
|
||||||
|
|
||||||
fn path(&self, _: Self::PathParameters) -> String {
|
fn path(&self, _: Self::PathParameters) -> String {
|
||||||
"/app/pot/default".to_string()
|
"/app/pot/default".to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::{Element, ElementContent, ElementId};
|
use crate::types::{Element, ElementContent, ElementId, PotId};
|
||||||
|
|
||||||
use super::UbisyncRequest;
|
use super::UbisyncRequest;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ElementCreateRequest {
|
pub struct ElementCreateRequest {
|
||||||
pub content: ElementContent,
|
pub content: ElementContent,
|
||||||
|
pub pot: Option<PotId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::{ElementContent, ElementId, MessageId};
|
use crate::types::{ElementContent, ElementId, MessageId, PotId};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
|
@ -22,6 +22,7 @@ pub enum MessageContent {
|
||||||
CreateElement {
|
CreateElement {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
content: ElementContent,
|
content: ElementContent,
|
||||||
|
pot: PotId,
|
||||||
},
|
},
|
||||||
SetElement {
|
SetElement {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
@ -30,6 +31,10 @@ pub enum MessageContent {
|
||||||
RemoveElement {
|
RemoveElement {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
},
|
},
|
||||||
|
AddPot {
|
||||||
|
id: PotId,
|
||||||
|
app_type: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{ElementContent, ElementId, MessageId};
|
use super::{ElementContent, ElementId, MessageId, PotId};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
// Uuid identifying the element itself
|
// Uuid identifying the element itself
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
pot: Option<PotId>,
|
||||||
content: ElementContent,
|
content: ElementContent,
|
||||||
latest_message: Option<MessageId>,
|
latest_message: Option<MessageId>,
|
||||||
local_changes: bool,
|
local_changes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(ElementId, ElementContent, Option<MessageId>, bool)> for Element {
|
|
||||||
fn from(value: (ElementId, ElementContent, Option<MessageId>, bool)) -> Self {
|
impl From<(ElementId, Option<PotId>, ElementContent, Option<MessageId>, bool)> for Element {
|
||||||
|
fn from(value: (ElementId, Option<PotId>, ElementContent, Option<MessageId>, bool)) -> Self {
|
||||||
Element {
|
Element {
|
||||||
id: value.0,
|
id: value.0,
|
||||||
content: value.1,
|
pot: value.1,
|
||||||
latest_message: value.2,
|
content: value.2,
|
||||||
local_changes: value.3,
|
latest_message: value.3,
|
||||||
|
local_changes: value.4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +30,7 @@ impl Element {
|
||||||
// A new element with no latest message must have local changes
|
// A new element with no latest message must have local changes
|
||||||
Element {
|
Element {
|
||||||
id: id,
|
id: id,
|
||||||
|
pot: None,
|
||||||
content: content,
|
content: content,
|
||||||
latest_message: None,
|
latest_message: None,
|
||||||
local_changes: true,
|
local_changes: true,
|
||||||
|
|
|
@ -4,8 +4,8 @@ use super::PotId;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Pot {
|
pub struct Pot {
|
||||||
id: PotId,
|
pub id: PotId,
|
||||||
app_type: String,
|
pub app_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pot {
|
impl Pot {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use jsonwebtoken::{decode, Header};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::chrono::{DateTime, Utc};
|
use serde_with::chrono::{DateTime, Utc};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
use ubisync_lib::{api::app::{AppRegisterRequest, AppRegisterResponse, AppSetDefaultPotRequest, AppSetDefaultPotResponse, AppCreatePotRequest, AppCreatePotResponse}, types::PotId};
|
use ubisync_lib::{api::app::{AppRegisterRequest, AppRegisterResponse, AppSetDefaultPotRequest, AppSetDefaultPotResponse, AppCreatePotRequest, AppCreatePotResponse, AppGetDefaultPotRequest, AppGetDefaultPotResponse}, types::PotId};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::state::ApiState;
|
use crate::state::ApiState;
|
||||||
|
@ -120,6 +120,20 @@ pub(super) async fn set_default_pot(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn get_default_pot(
|
||||||
|
s: Extension<Arc<ApiState>>,
|
||||||
|
app_id: Extension<AppId>,
|
||||||
|
Json(_): Json<AppGetDefaultPotRequest>,
|
||||||
|
) -> Response {
|
||||||
|
match s.get_default_pot(&app_id.0) {
|
||||||
|
Ok(p) => (StatusCode::OK, Json {0: AppGetDefaultPotResponse { pot: p }}).into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("No default pot found: {}", e);
|
||||||
|
StatusCode::NOT_FOUND.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn create_pot(
|
pub(super) async fn create_pot(
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
app_id: Extension<AppId>,
|
app_id: Extension<AppId>,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use axum::{
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::state::ApiState;
|
use crate::state::ApiState;
|
||||||
use ubisync_lib::{
|
use ubisync_lib::{
|
||||||
|
@ -17,8 +17,10 @@ use ubisync_lib::{
|
||||||
types::ElementId,
|
types::ElementId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
|
use super::app::AppId;
|
||||||
let element = s.get_element(&id);
|
|
||||||
|
pub(super) async fn get(Path(id): Path<ElementId>, app: Extension<AppId>, s: Extension<Arc<ApiState>>) -> Response {
|
||||||
|
let element = s.get_element(&id, &app);
|
||||||
match element {
|
match element {
|
||||||
Ok(el) => (
|
Ok(el) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
|
@ -27,15 +29,30 @@ pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
Err(e) => {
|
||||||
|
warn!("Could not get element: {}", e);
|
||||||
|
StatusCode::NOT_FOUND.into_response()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn create(
|
pub(super) async fn create(
|
||||||
|
app: Extension<AppId>,
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
Json(req): Json<ElementCreateRequest>,
|
Json(req): Json<ElementCreateRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let element_id = s.create_element(&req.content);
|
let pot_id = match req.pot {
|
||||||
|
Some(p) => p,
|
||||||
|
None => match s.get_default_pot(&app.0) {
|
||||||
|
Ok(p) => p.id,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Element create request did not provide pot id, and no default pot for requesting app was found: {}", e);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let element_id = s.create_element(&req.content, &pot_id);
|
||||||
debug!("{:?}", element_id);
|
debug!("{:?}", element_id);
|
||||||
match element_id {
|
match element_id {
|
||||||
Ok(id) => (
|
Ok(id) => (
|
||||||
|
@ -51,10 +68,11 @@ pub(super) async fn create(
|
||||||
|
|
||||||
pub(super) async fn set(
|
pub(super) async fn set(
|
||||||
Path(id): Path<ElementId>,
|
Path(id): Path<ElementId>,
|
||||||
|
app: Extension<AppId>,
|
||||||
s: Extension<Arc<ApiState>>,
|
s: Extension<Arc<ApiState>>,
|
||||||
Json(req): Json<ElementSetRequest>,
|
Json(req): Json<ElementSetRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let res = s.write_element_content(&id, &req.content);
|
let res = s.write_element_content(&id, &app, &req.content);
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => (
|
Ok(_) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
middleware::from_fn,
|
middleware::from_fn,
|
||||||
routing::{get, put, post},
|
routing::{get, put},
|
||||||
Extension, Router,
|
Extension, Router,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -14,7 +14,7 @@ pub fn get_router(state: ApiState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
// authenticated routes
|
// authenticated routes
|
||||||
.route("/app/pot", put(app::create_pot))
|
.route("/app/pot", put(app::create_pot))
|
||||||
.route("/app/pot/default", post(app::set_default_pot))
|
.route("/app/pot/default", get(app::get_default_pot).post(app::set_default_pot))
|
||||||
.route("/element", put(element::create))
|
.route("/element", put(element::create))
|
||||||
.route(
|
.route(
|
||||||
"/element/:id",
|
"/element/:id",
|
||||||
|
|
|
@ -12,9 +12,9 @@ pub fn handle(state: &CommState, peer: &PeerId, message: Message) {
|
||||||
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 , pot} => {
|
||||||
state
|
state
|
||||||
.add_received_element(id, content, message.id())
|
.add_received_element(id, content, message.id(), pot)
|
||||||
.expect("State failed");
|
.expect("State failed");
|
||||||
}
|
}
|
||||||
MessageContent::SetElement { id, content } => {
|
MessageContent::SetElement { id, content } => {
|
||||||
|
@ -24,6 +24,11 @@ pub fn handle(state: &CommState, peer: &PeerId, message: Message) {
|
||||||
}
|
}
|
||||||
MessageContent::RemoveElement { id } => {
|
MessageContent::RemoveElement { id } => {
|
||||||
state.remove_element(id).expect("State failed");
|
state.remove_element(id).expect("State failed");
|
||||||
|
},
|
||||||
|
MessageContent::AddPot { id, app_type } => {
|
||||||
|
state.add_pot(id, app_type).expect("State failed");
|
||||||
|
//TODO: remove when setting default pot properly is possible
|
||||||
|
let _ = state.set_default_pot_for_all_apps(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
use cozo::DbInstance;
|
use cozo::DbInstance;
|
||||||
use jsonwebtoken::{DecodingKey, EncodingKey, Validation};
|
use jsonwebtoken::{DecodingKey, EncodingKey, Validation};
|
||||||
use serde_with::chrono::Utc;
|
use serde_with::chrono::Utc;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use ubisync_lib::{
|
use ubisync_lib::{
|
||||||
messages::MessageContent,
|
messages::MessageContent,
|
||||||
types::{Element, ElementContent, ElementId, PotId},
|
types::{Element, ElementContent, ElementId, PotId, Pot},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -52,14 +53,19 @@ impl ApiState {
|
||||||
queries::apps::get(self.db(), id)
|
queries::apps::get(self.db(), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> {
|
pub fn create_element(
|
||||||
|
&self,
|
||||||
|
content: &ElementContent,
|
||||||
|
pot: &PotId,
|
||||||
|
) -> anyhow::Result<ElementId> {
|
||||||
let id = ElementId::new();
|
let id = ElementId::new();
|
||||||
queries::elements::add(self.db(), &id, &content, None, true)?;
|
queries::elements::add(self.db(), &id, &content, None, true, pot)?;
|
||||||
debug!("Added element {{{}}}", &id.to_string());
|
debug!("Added element {{{}}}", &id.to_string());
|
||||||
|
|
||||||
self.state.send_to_peers(MessageContent::CreateElement {
|
self.state.send_to_peers(MessageContent::CreateElement {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
content: content.clone(),
|
content: content.clone(),
|
||||||
|
pot: pot.clone(),
|
||||||
});
|
});
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
@ -67,13 +73,21 @@ impl ApiState {
|
||||||
pub fn write_element_content(
|
pub fn write_element_content(
|
||||||
&self,
|
&self,
|
||||||
id: &ElementId,
|
id: &ElementId,
|
||||||
|
app: &AppId,
|
||||||
content: &ElementContent,
|
content: &ElementContent,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
queries::elements::set_content(self.db(), id, content)?;
|
if queries::elements::get_app_access(self.db(), id, app)? {
|
||||||
queries::elements::set_local_changes(self.db(), id, true)?;
|
queries::elements::set_content(self.db(), id, content)?;
|
||||||
debug!("Wrote element content {{{}}}", &id.to_string());
|
queries::elements::set_local_changes(self.db(), id, true)?;
|
||||||
|
debug!("Wrote element content {{{}}}", &id.to_string());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(Error::msg(
|
||||||
|
"Element does not exist or app does not have access to it",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_element(&self, id: &ElementId) -> anyhow::Result<()> {
|
pub fn remove_element(&self, id: &ElementId) -> anyhow::Result<()> {
|
||||||
|
@ -87,14 +101,26 @@ impl ApiState {
|
||||||
queries::apps::set_default_pot(self.db(), app_id, pot_id)
|
queries::apps::set_default_pot(self.db(), app_id, pot_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_default_pot(&self, app_id: &AppId) -> anyhow::Result<Pot> {
|
||||||
|
queries::apps::get_default_pot(self.db(), app_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_pot(&self, app_id: &AppId, app_type: &str) -> anyhow::Result<PotId> {
|
pub fn create_pot(&self, app_id: &AppId, app_type: &str) -> anyhow::Result<PotId> {
|
||||||
let pot_id = PotId::new();
|
let pot_id = PotId::new();
|
||||||
queries::apps::create_pot(self.db(), &pot_id, app_id, app_type)?;
|
queries::apps::create_pot(self.db(), &pot_id, app_id, app_type)?;
|
||||||
|
|
||||||
|
self.state.send_to_peers(MessageContent::AddPot { id: pot_id.to_owned(), app_type: app_type.to_string() });
|
||||||
Ok(pot_id)
|
Ok(pot_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_element(&self, id: &ElementId) -> anyhow::Result<Element> {
|
pub fn get_element(&self, id: &ElementId, app: &AppId) -> anyhow::Result<Element> {
|
||||||
self.state.get_element(id)
|
if queries::elements::get_app_access(self.db(), id, app)? {
|
||||||
|
self.state.get_element(id)
|
||||||
|
} else {
|
||||||
|
Err(Error::msg(
|
||||||
|
"Element does not exist or app does not have access to it",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db(&self) -> &DbInstance {
|
fn db(&self) -> &DbInstance {
|
||||||
|
@ -135,10 +161,12 @@ mod tests {
|
||||||
State::new("mem").await.unwrap(),
|
State::new("mem").await.unwrap(),
|
||||||
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
);
|
);
|
||||||
|
let app_id = state.add_app("appname", "appdesc", "apptype").unwrap();
|
||||||
|
let pot_id = state.create_pot(&app_id, "apptype").unwrap();
|
||||||
let id = state
|
let id = state
|
||||||
.create_element(&ElementContent::Text("Test-text".to_string()))
|
.create_element(&ElementContent::Text("Test-text".to_string()), &pot_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let el = state.get_element(&id).unwrap();
|
let el = state.get_element(&id, &app_id).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ElementContent::Text("Test-text".to_string()),
|
ElementContent::Text("Test-text".to_string()),
|
||||||
el.content().to_owned()
|
el.content().to_owned()
|
||||||
|
@ -157,13 +185,15 @@ mod tests {
|
||||||
State::new("mem").await.unwrap(),
|
State::new("mem").await.unwrap(),
|
||||||
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
);
|
);
|
||||||
|
let app_id = state.add_app("appname", "appdesc", "apptype").unwrap();
|
||||||
|
let pot_id = state.create_pot(&app_id, "apptype").unwrap();
|
||||||
let id = state
|
let id = state
|
||||||
.create_element(&ElementContent::Text("Test-text".to_string()))
|
.create_element(&ElementContent::Text("Test-text".to_string()), &pot_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state
|
state
|
||||||
.write_element_content(&id, &ElementContent::Text("Test-text 2".to_string()))
|
.write_element_content(&id, &app_id, &ElementContent::Text("Test-text 2".to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let el = state.get_element(&id).unwrap();
|
let el = state.get_element(&id, &app_id).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ElementContent::Text("Test-text 2".to_string()),
|
ElementContent::Text("Test-text 2".to_string()),
|
||||||
el.content().to_owned()
|
el.content().to_owned()
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
use cozo::DbInstance;
|
use cozo::DbInstance;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use ubisync_lib::{
|
use ubisync_lib::{
|
||||||
peer::Peer,
|
peer::Peer,
|
||||||
types::{Element, ElementContent, ElementId, MessageId, PeerId},
|
types::{Element, ElementContent, ElementId, MessageId, PeerId, PotId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::state::queries;
|
use crate::state::queries;
|
||||||
|
@ -27,6 +28,7 @@ impl CommState {
|
||||||
id: &ElementId,
|
id: &ElementId,
|
||||||
content: &ElementContent,
|
content: &ElementContent,
|
||||||
latest_message: &MessageId,
|
latest_message: &MessageId,
|
||||||
|
pot_id: &PotId,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
queries::elements::add(
|
queries::elements::add(
|
||||||
self.db(),
|
self.db(),
|
||||||
|
@ -34,6 +36,7 @@ impl CommState {
|
||||||
&content,
|
&content,
|
||||||
Some(latest_message.to_owned()),
|
Some(latest_message.to_owned()),
|
||||||
false,
|
false,
|
||||||
|
pot_id,
|
||||||
)?;
|
)?;
|
||||||
debug!("Added element {{{}}}", &id.to_string());
|
debug!("Added element {{{}}}", &id.to_string());
|
||||||
|
|
||||||
|
@ -76,6 +79,25 @@ impl CommState {
|
||||||
self.state.get_peers()
|
self.state.get_peers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_pot(&self, id: &PotId, app_type: &str) -> anyhow::Result<()> {
|
||||||
|
queries::pots::add(self.db(), id, app_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default_pot_for_all_apps(&self, id: &PotId) -> anyhow::Result<()> {
|
||||||
|
if let Ok(apps) = queries::apps::get_all_ids(self.db()) {
|
||||||
|
for app in apps {
|
||||||
|
let res = queries::apps::set_default_pot(self.db(), &app, id);
|
||||||
|
if let Ok(_) = res {
|
||||||
|
debug!("Set {:?} as default for app {:?}", id, &app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(Error::msg("Could not fetch list of all apps"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn db(&self) -> &DbInstance {
|
fn db(&self) -> &DbInstance {
|
||||||
&self.state.db
|
&self.state.db
|
||||||
}
|
}
|
||||||
|
@ -88,7 +110,7 @@ mod tests {
|
||||||
use super::CommState;
|
use super::CommState;
|
||||||
|
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
use ubisync_lib::types::{ElementContent, ElementId, MessageId};
|
use ubisync_lib::types::{ElementContent, ElementId, MessageId, PotId};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
|
@ -100,11 +122,13 @@ mod tests {
|
||||||
|
|
||||||
let state = CommState::new(State::new("mem").await.unwrap());
|
let state = CommState::new(State::new("mem").await.unwrap());
|
||||||
let id = ElementId::new();
|
let id = ElementId::new();
|
||||||
|
let pot_id = PotId::new();
|
||||||
state
|
state
|
||||||
.add_received_element(
|
.add_received_element(
|
||||||
&id,
|
&id,
|
||||||
&ElementContent::Text("Test-text".to_string()),
|
&ElementContent::Text("Test-text".to_string()),
|
||||||
&MessageId::new(),
|
&MessageId::new(),
|
||||||
|
&pot_id,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let el = state.get_element(&id).unwrap();
|
let el = state.get_element(&id).unwrap();
|
||||||
|
@ -124,11 +148,13 @@ mod tests {
|
||||||
|
|
||||||
let state = CommState::new(State::new("mem").await.unwrap());
|
let state = CommState::new(State::new("mem").await.unwrap());
|
||||||
let id = ElementId::new();
|
let id = ElementId::new();
|
||||||
|
let pot_id = PotId::new();
|
||||||
state
|
state
|
||||||
.add_received_element(
|
.add_received_element(
|
||||||
&id,
|
&id,
|
||||||
&ElementContent::Text("Test-text".to_string()),
|
&ElementContent::Text("Test-text".to_string()),
|
||||||
&MessageId::new(),
|
&MessageId::new(),
|
||||||
|
&pot_id,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state
|
state
|
||||||
|
|
|
@ -3,9 +3,13 @@ use std::collections::BTreeMap;
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use cozo::{DataValue, DbInstance, Num, ScriptMutability};
|
use cozo::{DataValue, DbInstance, Num, ScriptMutability};
|
||||||
use serde_with::chrono::{DateTime, Utc};
|
use serde_with::chrono::{DateTime, Utc};
|
||||||
use ubisync_lib::types::PotId;
|
use tracing::warn;
|
||||||
|
use ubisync_lib::types::{Pot, PotId};
|
||||||
|
|
||||||
use crate::{api::v0::app::{AppId, App}, run_query};
|
use crate::{
|
||||||
|
api::v0::app::{App, AppId},
|
||||||
|
run_query,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn add(
|
pub fn add(
|
||||||
db: &DbInstance,
|
db: &DbInstance,
|
||||||
|
@ -61,6 +65,30 @@ pub fn exists(db: &DbInstance, id: &AppId) -> anyhow::Result<bool> {
|
||||||
Err(Error::msg("Could not check whether app is registered"))
|
Err(Error::msg("Could not check whether app is registered"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_ids(db: &DbInstance) -> anyhow::Result<Vec<AppId>> {
|
||||||
|
let result = db.run_script(
|
||||||
|
"?[id] := *apps{id}",
|
||||||
|
BTreeMap::new(),
|
||||||
|
ScriptMutability::Immutable,
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(named_rows) => Ok(named_rows
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.filter_map(|row| {
|
||||||
|
if let [DataValue::Str(app_id)] = row.as_slice() {
|
||||||
|
serde_json::from_str::<AppId>(app_id).ok()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()),
|
||||||
|
Err(e) => bail!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(db: &DbInstance, id: &AppId) -> anyhow::Result<App> {
|
pub fn get(db: &DbInstance, id: &AppId) -> anyhow::Result<App> {
|
||||||
let mut params = BTreeMap::new();
|
let mut params = BTreeMap::new();
|
||||||
params.insert(
|
params.insert(
|
||||||
|
@ -76,13 +104,23 @@ pub fn get(db: &DbInstance, id: &AppId) -> anyhow::Result<App> {
|
||||||
|
|
||||||
if let Ok(rows) = result {
|
if let Ok(rows) = result {
|
||||||
if let Some(firstrow) = rows.rows.first() {
|
if let Some(firstrow) = rows.rows.first() {
|
||||||
if let [DataValue::Num(Num::Int(ts)), DataValue::Str(app_type), DataValue::Str(name), DataValue::Str(desc), default_pot] = firstrow.as_slice() {
|
if let [DataValue::Num(Num::Int(ts)), DataValue::Str(app_type), DataValue::Str(name), DataValue::Str(desc), default_pot] =
|
||||||
let last_access = DateTime::from_timestamp(ts.to_owned(), 0).ok_or(Error::msg("Failed to deserialize timestamp"))?;
|
firstrow.as_slice()
|
||||||
|
{
|
||||||
|
let last_access = DateTime::from_timestamp(ts.to_owned(), 0)
|
||||||
|
.ok_or(Error::msg("Failed to deserialize timestamp"))?;
|
||||||
let pot_id = match default_pot {
|
let pot_id = match default_pot {
|
||||||
DataValue::Str(dpid) => Some(serde_json::from_str(dpid)?),
|
DataValue::Str(dpid) => Some(serde_json::from_str(dpid)?),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
return Ok(App {id: id.clone(), app_type: app_type.to_string(), last_access, name: name.to_string(), description: desc.to_string(), default_pot: pot_id});
|
return Ok(App {
|
||||||
|
id: id.clone(),
|
||||||
|
app_type: app_type.to_string(),
|
||||||
|
last_access,
|
||||||
|
name: name.to_string(),
|
||||||
|
description: desc.to_string(),
|
||||||
|
default_pot: pot_id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,35 +131,124 @@ pub fn get(db: &DbInstance, id: &AppId) -> anyhow::Result<App> {
|
||||||
pub fn set_default_pot(db: &DbInstance, id: &AppId, pot: &PotId) -> anyhow::Result<()> {
|
pub fn set_default_pot(db: &DbInstance, id: &AppId, pot: &PotId) -> 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())),
|
||||||
("default_pot", DataValue::Str(serde_json::to_string(pot)?.into()))
|
(
|
||||||
|
"default_pot",
|
||||||
|
DataValue::Str(serde_json::to_string(pot)?.into()),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
match run_query!(
|
match run_query!(
|
||||||
&db,
|
&db, ":update apps {id => default_pot}",
|
||||||
":update apps {id => default_pot}",
|
params.clone(),
|
||||||
params,
|
|
||||||
ScriptMutability::Mutable
|
ScriptMutability::Mutable
|
||||||
) {
|
) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => match run_query!(&db, ":put pot_memberships {pot_id = default_pot, app_id = id}", params, ScriptMutability::Mutable) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(report) => bail!(report),
|
||||||
|
},
|
||||||
Err(report) => bail!(report),
|
Err(report) => bail!(report),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_pot(db: &DbInstance, pot_id: &PotId, app_id: &AppId, app_type: &str) -> anyhow::Result<()> {
|
pub fn get_default_pot(db: &DbInstance, app: &AppId) -> anyhow::Result<Pot> {
|
||||||
|
let mut params = BTreeMap::new();
|
||||||
|
params.insert(
|
||||||
|
"app_id".to_string(),
|
||||||
|
DataValue::Str(serde_json::to_string(&app)?.into()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = db.run_script(
|
||||||
|
"
|
||||||
|
default_pot[pot_id] := *apps{id: $app_id, default_pot: pot_id}
|
||||||
|
?[pot_id, app_type] := default_pot[pot_id], *pots[pot_id, app_type]
|
||||||
|
",
|
||||||
|
params.clone(),
|
||||||
|
ScriptMutability::Immutable,
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(rows) => {
|
||||||
|
if let Some(firstrow) = rows.rows.first() {
|
||||||
|
if let [DataValue::Str(pot_id_str), DataValue::Str(app_type)] = firstrow.as_slice()
|
||||||
|
{
|
||||||
|
Ok(Pot {
|
||||||
|
id: serde_json::from_str(&pot_id_str)?,
|
||||||
|
app_type: app_type.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("Failed to deserialize query result"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("App not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => bail!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pot(
|
||||||
|
db: &DbInstance,
|
||||||
|
pot_id: &PotId,
|
||||||
|
app_id: &AppId,
|
||||||
|
app_type: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let params = vec![
|
let params = vec![
|
||||||
("pot_id", DataValue::Str(serde_json::to_string(pot_id)?.into())),
|
(
|
||||||
("app_id", DataValue::Str(serde_json::to_string(app_id)?.into())),
|
"pot_id",
|
||||||
|
DataValue::Str(serde_json::to_string(pot_id)?.into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"app_id",
|
||||||
|
DataValue::Str(serde_json::to_string(app_id)?.into()),
|
||||||
|
),
|
||||||
("app_type", DataValue::Str(app_type.into())),
|
("app_type", DataValue::Str(app_type.into())),
|
||||||
];
|
];
|
||||||
|
|
||||||
match run_query!(
|
match run_query!(
|
||||||
&db,
|
&db,
|
||||||
":insert pots {pot_id => app_type}
|
":insert pots {id = pot_id => app_type}"
|
||||||
:insert pot_memberships {pot_id => app_id}",
|
,
|
||||||
params,
|
params.clone(),
|
||||||
ScriptMutability::Mutable
|
ScriptMutability::Mutable
|
||||||
) {
|
) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => match run_query!(&db, ":insert pot_memberships {pot_id => app_id}", params, ScriptMutability::Mutable) {
|
||||||
Err(e) => bail!(e),
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
bail!(e)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
bail!(e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use cozo::DbInstance;
|
||||||
|
use serde_with::chrono::Utc;
|
||||||
|
use tracing::{Level, debug};
|
||||||
|
use ubisync_lib::types::PotId;
|
||||||
|
|
||||||
|
use crate::{state::{schema, queries::pots}, api::v0::app::AppId};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn default_pot() {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.pretty()
|
||||||
|
.with_max_level(Level::DEBUG)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let db = DbInstance::new("mem", "", Default::default()).unwrap();
|
||||||
|
schema::add_schema(&db).unwrap();
|
||||||
|
let app_id = AppId::new();
|
||||||
|
let pot_id = PotId::new();
|
||||||
|
super::add(&db, &app_id, &Utc::now(), "name", "description", "app_type").unwrap();
|
||||||
|
pots::add(&db, &pot_id, "app_type").unwrap();
|
||||||
|
super::set_default_pot(&db, &app_id, &pot_id).unwrap();
|
||||||
|
|
||||||
|
debug!("Result: {:?}", super::get_default_pot(&db, &app_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,10 +7,10 @@ use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
run_query,
|
run_query,
|
||||||
state::{Element, ElementContent, ElementId},
|
state::{Element, ElementContent, ElementId}, api::v0::app::AppId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ubisync_lib::types::{MessageId, Tag};
|
use ubisync_lib::types::{MessageId, Tag, PotId};
|
||||||
|
|
||||||
pub fn add(
|
pub fn add(
|
||||||
db: &DbInstance,
|
db: &DbInstance,
|
||||||
|
@ -18,6 +18,7 @@ pub fn add(
|
||||||
content: &ElementContent,
|
content: &ElementContent,
|
||||||
latest_message: Option<MessageId>,
|
latest_message: Option<MessageId>,
|
||||||
local_changes: bool,
|
local_changes: bool,
|
||||||
|
pot: &PotId,
|
||||||
) -> anyhow::Result<()> {
|
) -> 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())),
|
||||||
|
@ -33,11 +34,12 @@ pub fn add(
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
("local_changes", DataValue::Bool(local_changes)),
|
("local_changes", DataValue::Bool(local_changes)),
|
||||||
|
("pot", DataValue::Str(serde_json::to_string(pot)?.into())),
|
||||||
];
|
];
|
||||||
|
|
||||||
match run_query!(
|
match run_query!(
|
||||||
&db,
|
&db,
|
||||||
":insert elements {id => content, latest_message, local_changes}",
|
":insert elements {id => content, latest_message, local_changes, pot}",
|
||||||
params,
|
params,
|
||||||
ScriptMutability::Mutable
|
ScriptMutability::Mutable
|
||||||
) {
|
) {
|
||||||
|
@ -103,17 +105,18 @@ pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
|
||||||
);
|
);
|
||||||
|
|
||||||
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, pot] := *elements[$id, content, latest_message, local_changes, pot]
|
||||||
", params, cozo::ScriptMutability::Immutable);
|
", params, cozo::ScriptMutability::Immutable);
|
||||||
match result {
|
match result {
|
||||||
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)] =
|
if let [DataValue::Json(JsonData(content)), latest_message, DataValue::Bool(local_changes), DataValue::Str(pot_id)] =
|
||||||
firstrow.as_slice()
|
firstrow.as_slice()
|
||||||
{
|
{
|
||||||
return Ok(Element::from((
|
return Ok(Element::from((
|
||||||
id.to_owned(),
|
id.to_owned(),
|
||||||
|
Some(serde_json::from_str(pot_id)?),
|
||||||
serde_json::from_value(content.to_owned())?,
|
serde_json::from_value(content.to_owned())?,
|
||||||
match latest_message {
|
match latest_message {
|
||||||
DataValue::Str(s) => Some(serde_json::from_str(s)?),
|
DataValue::Str(s) => Some(serde_json::from_str(s)?),
|
||||||
|
@ -131,6 +134,27 @@ pub fn get(db: &DbInstance, id: &ElementId) -> anyhow::Result<Element> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_app_access(db: &DbInstance, id: &ElementId, app: &AppId) -> anyhow::Result<bool> {
|
||||||
|
let mut params = BTreeMap::new();
|
||||||
|
params.insert(
|
||||||
|
"id".to_string(),
|
||||||
|
DataValue::Str(serde_json::to_string(&id)?.into()),
|
||||||
|
);
|
||||||
|
params.insert("app_id".to_string(), DataValue::Str(serde_json::to_string(&app)?.into()));
|
||||||
|
|
||||||
|
let result = db.run_script("
|
||||||
|
memberships[pot_id] := *pot_memberships{pot_id, app_id}
|
||||||
|
?[id] := memberships[pot_id], *elements{id, pot: pot_id}
|
||||||
|
", params.clone(), cozo::ScriptMutability::Immutable);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(named_rows) => {
|
||||||
|
Ok(named_rows.rows.len() > 0)
|
||||||
|
},
|
||||||
|
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(
|
params.insert(
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> {
|
||||||
content: Json,
|
content: Json,
|
||||||
latest_message: String?,
|
latest_message: String?,
|
||||||
local_changes: Bool,
|
local_changes: Bool,
|
||||||
|
pot: String,
|
||||||
}}
|
}}
|
||||||
{:create tags {
|
{:create tags {
|
||||||
tag: String,
|
tag: String,
|
||||||
|
|
|
@ -30,17 +30,17 @@ async fn two_nodes_element_creation() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let api_client2 = UbisyncClient::init("localhost", 9982, None, "App", "Long desc", "test-app-type")
|
let api_client2 = UbisyncClient::init("localhost", 9982, None, "App", "Long desc", "test-app-type")
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.create_default_pot()
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_millis(5000)).await;
|
||||||
|
|
||||||
let test_element_content = ElementContent::Text("Text".to_string());
|
let test_element_content = ElementContent::Text("Text".to_string());
|
||||||
let create_resp = api_client1
|
let create_resp = api_client1
|
||||||
.send(
|
.send(
|
||||||
ElementCreateRequest {
|
ElementCreateRequest {
|
||||||
content: test_element_content.clone(),
|
content: test_element_content.clone(),
|
||||||
|
pot: None,
|
||||||
},
|
},
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue