208 lines
6 KiB
Rust
208 lines
6 KiB
Rust
use std::sync::Arc;
|
|
|
|
use anyhow::Error;
|
|
use cozo::DbInstance;
|
|
use jsonwebtoken::{DecodingKey, EncodingKey, Validation};
|
|
use serde_with::chrono::Utc;
|
|
use tracing::debug;
|
|
use ubisync_lib::{
|
|
messages::MessageContent,
|
|
types::{Element, ElementContent, ElementId, Pot, PotId},
|
|
};
|
|
|
|
use crate::{
|
|
api::v0::app::{App, AppId},
|
|
state::queries,
|
|
};
|
|
|
|
use super::State;
|
|
|
|
pub struct ApiState {
|
|
state: Arc<State>,
|
|
jwt_encoding_key: EncodingKey,
|
|
jwt_decoding_key: DecodingKey,
|
|
jwt_validation: Validation,
|
|
}
|
|
|
|
impl ApiState {
|
|
pub fn new(state: Arc<State>, jwt_secret: &str) -> Self {
|
|
let mut validation = Validation::default();
|
|
validation.set_required_spec_claims(&vec!["sub"]);
|
|
ApiState {
|
|
state: state,
|
|
jwt_encoding_key: EncodingKey::from_secret(jwt_secret.as_bytes()),
|
|
jwt_decoding_key: DecodingKey::from_secret(jwt_secret.as_bytes()),
|
|
jwt_validation: validation,
|
|
}
|
|
}
|
|
|
|
pub fn add_app(&self, name: &str, description: &str, app_type: &str) -> anyhow::Result<AppId> {
|
|
let id = AppId::new();
|
|
let last_access = Utc::now();
|
|
queries::apps::add(self.db(), &id, &last_access, name, description, app_type)?;
|
|
debug!("Successfully added app");
|
|
|
|
Ok(id)
|
|
}
|
|
|
|
pub fn app_exists(&self, id: &AppId) -> anyhow::Result<bool> {
|
|
queries::apps::exists(self.db(), id)
|
|
}
|
|
|
|
pub fn get_app(&self, id: &AppId) -> anyhow::Result<App> {
|
|
queries::apps::get(self.db(), id)
|
|
}
|
|
|
|
pub fn create_element(
|
|
&self,
|
|
content: &ElementContent,
|
|
pot: &PotId,
|
|
) -> anyhow::Result<ElementId> {
|
|
let id = ElementId::new();
|
|
queries::elements::add(self.db(), &id, &content, None, true, pot)?;
|
|
debug!("Added element {{{}}}", &id.to_string());
|
|
|
|
self.state.send_to_peers(MessageContent::CreateElement {
|
|
id: id.clone(),
|
|
content: content.clone(),
|
|
pot: pot.clone(),
|
|
});
|
|
Ok(id)
|
|
}
|
|
|
|
pub fn write_element_content(
|
|
&self,
|
|
id: &ElementId,
|
|
app: &AppId,
|
|
content: &ElementContent,
|
|
) -> anyhow::Result<()> {
|
|
if queries::elements::get_app_access(self.db(), id, app)? {
|
|
queries::elements::set_content(self.db(), id, content)?;
|
|
queries::elements::set_local_changes(self.db(), id, true)?;
|
|
debug!("Wrote element content {{{}}}", &id.to_string());
|
|
|
|
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<()> {
|
|
let res = self.state.remove_element(id);
|
|
debug!("Removed element {{{}}}", &id.to_string());
|
|
|
|
res
|
|
}
|
|
|
|
pub fn set_app_default_pot(&self, app_id: &AppId, pot_id: &PotId) -> anyhow::Result<()> {
|
|
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> {
|
|
let pot_id = PotId::new();
|
|
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)
|
|
}
|
|
|
|
pub fn get_element(&self, id: &ElementId, app: &AppId) -> anyhow::Result<Element> {
|
|
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 {
|
|
&self.state.db
|
|
}
|
|
|
|
pub fn jwt_encoding_key(&self) -> &EncodingKey {
|
|
&self.jwt_encoding_key
|
|
}
|
|
|
|
pub fn jwt_decoding_key(&self) -> &DecodingKey {
|
|
&self.jwt_decoding_key
|
|
}
|
|
|
|
pub fn jwt_validation(&self) -> &Validation {
|
|
&self.jwt_validation
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use tracing::Level;
|
|
use ubisync_lib::types::ElementContent;
|
|
|
|
use crate::state::State;
|
|
|
|
use super::ApiState;
|
|
|
|
#[tokio::test]
|
|
#[serial_test::serial]
|
|
async fn test_element_create() {
|
|
tracing_subscriber::fmt()
|
|
.pretty()
|
|
.with_max_level(Level::DEBUG)
|
|
.init();
|
|
|
|
let state = ApiState::new(
|
|
State::new("mem").await.unwrap(),
|
|
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
|
);
|
|
let app_id = state.add_app("appname", "appdesc", "apptype").unwrap();
|
|
let pot_id = state.create_pot(&app_id, "apptype").unwrap();
|
|
let id = state
|
|
.create_element(&ElementContent::Text("Test-text".to_string()), &pot_id)
|
|
.unwrap();
|
|
let el = state.get_element(&id, &app_id).unwrap();
|
|
assert_eq!(
|
|
ElementContent::Text("Test-text".to_string()),
|
|
el.content().to_owned()
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial_test::serial]
|
|
async fn test_element_write() {
|
|
tracing_subscriber::fmt()
|
|
.pretty()
|
|
.with_max_level(Level::DEBUG)
|
|
.init();
|
|
|
|
let state = ApiState::new(
|
|
State::new("mem").await.unwrap(),
|
|
"abcdabcdabcdabcdabcdabcdabcdabcd",
|
|
);
|
|
let app_id = state.add_app("appname", "appdesc", "apptype").unwrap();
|
|
let pot_id = state.create_pot(&app_id, "apptype").unwrap();
|
|
let id = state
|
|
.create_element(&ElementContent::Text("Test-text".to_string()), &pot_id)
|
|
.unwrap();
|
|
state
|
|
.write_element_content(
|
|
&id,
|
|
&app_id,
|
|
&ElementContent::Text("Test-text 2".to_string()),
|
|
)
|
|
.unwrap();
|
|
let el = state.get_element(&id, &app_id).unwrap();
|
|
assert_eq!(
|
|
ElementContent::Text("Test-text 2".to_string()),
|
|
el.content().to_owned()
|
|
)
|
|
}
|
|
}
|