Database handle remodeling

This commit is contained in:
Philip (a-0) 2024-03-24 18:25:29 +01:00
parent e0b83d30b6
commit f2c9fe48a1
11 changed files with 489 additions and 361 deletions

View file

@ -41,19 +41,22 @@ impl ApiState {
app_type: String, app_type: String,
) -> anyhow::Result<AppId> { ) -> anyhow::Result<AppId> {
let id = AppId::new(); let id = AppId::new();
self.db().add_app(id.clone(), name, description, app_type)?; self.db()
.apps()
.add(id.clone(), name, description, app_type)?;
debug!("Successfully added app"); debug!("Successfully added app");
Ok(id) Ok(id)
} }
pub fn app_exists(&self, id: AppId) -> anyhow::Result<bool> { pub fn app_exists(&self, id: AppId) -> anyhow::Result<bool> {
Ok(self.db().get_app(id)?.is_some()) Ok(self.db().apps().get(id)?.is_some())
} }
pub fn get_app(&self, id: AppId) -> anyhow::Result<App> { pub fn get_app(&self, id: AppId) -> anyhow::Result<App> {
self.db() self.db()
.get_app(id) .apps()
.get(id)
.map(|app_opt| app_opt.ok_or(Error::msg("Failed to find app")))? .map(|app_opt| app_opt.ok_or(Error::msg("Failed to find app")))?
} }
@ -64,7 +67,7 @@ impl ApiState {
update_strategy: ContentUpdateStrategy, update_strategy: ContentUpdateStrategy,
) -> anyhow::Result<ElementId> { ) -> anyhow::Result<ElementId> {
let id = ElementId::new(); let id = ElementId::new();
self.db().add_element( self.db().elements().add(
id.clone(), id.clone(),
content.clone(), content.clone(),
update_strategy, update_strategy,
@ -82,7 +85,7 @@ impl ApiState {
}, },
self.state self.state
.own_peer_id() .own_peer_id()
.map(|id| self.db().get_peer_family_members(id).unwrap_or_default()) .map(|id| self.db().families().get_members(id).unwrap_or_default())
.unwrap_or_default(), .unwrap_or_default(),
); );
@ -95,9 +98,9 @@ impl ApiState {
app: AppId, app: AppId,
content: ElementContent, content: ElementContent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if self.db().app_has_access(app, id.clone())? { if self.db().apps().has_access(app, id.clone())? {
self.db().set_element_content(id.clone(), content)?; self.db().elements().set_content(id.clone(), content)?;
self.db().set_element_local_changes(id.clone(), true)?; self.db().elements().set_local_changes(id.clone(), true)?;
debug!("Wrote element content {{{}}}", id.to_string()); debug!("Wrote element content {{{}}}", id.to_string());
Ok(()) Ok(())
@ -110,22 +113,23 @@ impl ApiState {
pub fn remove_element(&self, id: ElementId) -> anyhow::Result<()> { pub fn remove_element(&self, id: ElementId) -> anyhow::Result<()> {
self.db() self.db()
.remove_element(id.clone()) .elements()
.remove(id.clone())
.inspect(|_| debug!("Removed element {{{}}}", id.to_string())) .inspect(|_| debug!("Removed element {{{}}}", id.to_string()))
} }
pub fn set_app_default_pot(&self, app_id: AppId, pot_id: PotId) -> anyhow::Result<()> { pub fn set_app_default_pot(&self, app_id: AppId, pot_id: PotId) -> anyhow::Result<()> {
self.db().set_default_pot(app_id, pot_id) self.db().apps().set_default_pot(app_id, pot_id)
} }
pub fn get_default_pot(&self, app_id: AppId) -> anyhow::Result<Option<Pot>> { pub fn get_default_pot(&self, app_id: AppId) -> anyhow::Result<Option<Pot>> {
self.db().get_default_pot(app_id) self.db().apps().get_default_pot(app_id)
} }
pub fn create_pot(&self, app_id: AppId, app_type: String) -> anyhow::Result<PotId> { pub fn create_pot(&self, app_id: AppId, app_type: String) -> anyhow::Result<PotId> {
let pot_id = PotId::new(); let pot_id = PotId::new();
self.db().add_pot(pot_id.clone(), app_type.clone())?; self.db().pots().add(pot_id.clone(), app_type.clone())?;
self.db().add_pot_membership(pot_id.clone(), app_id)?; self.db().pot_memberships().add(pot_id.clone(), app_id)?;
self.state.send_to_peers( self.state.send_to_peers(
MessageContent::AddPot { MessageContent::AddPot {
@ -134,16 +138,17 @@ impl ApiState {
}, },
self.state self.state
.own_peer_id() .own_peer_id()
.map(|id| self.db().get_peer_family_members(id).unwrap_or_default()) .map(|id| self.db().families().get_members(id).unwrap_or_default())
.unwrap_or_default(), .unwrap_or_default(),
); );
Ok(pot_id) Ok(pot_id)
} }
pub fn get_element(&self, id: ElementId, app: AppId) -> anyhow::Result<Element> { pub fn get_element(&self, id: ElementId, app: AppId) -> anyhow::Result<Element> {
if self.db().app_has_access(app, id.clone())? { if self.db().apps().has_access(app, id.clone())? {
self.db() self.db()
.get_element(id) .elements()
.get(id)
.ok_or(Error::msg("Could not get element")) .ok_or(Error::msg("Could not get element"))
} else { } else {
Err(Error::msg( Err(Error::msg(

View file

@ -33,7 +33,8 @@ impl CommState {
pot_id: PotId, pot_id: PotId,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.db() self.db()
.add_element( .elements()
.add(
id.clone(), id.clone(),
content, content,
update_strategy, update_strategy,
@ -51,14 +52,15 @@ impl CommState {
latest_message: MessageId, latest_message: MessageId,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
//TODO: resolve potential conflicts with local changes //TODO: resolve potential conflicts with local changes
self.db().set_element_content(id.clone(), content)?; self.db().elements().set_content(id.clone(), content)?;
self.db() self.db()
.set_element_latest_message(id.clone(), Some(latest_message))?; .elements()
.set_latest_message(id.clone(), Some(latest_message))?;
debug!("Updated element {{{}}}", id.to_string()); debug!("Updated element {{{}}}", id.to_string());
if let Some(el) = self.db().get_element(id.clone()) { if let Some(el) = self.db().elements().get(id.clone()) {
if let Some(pot) = el.pot() { if let Some(pot) = el.pot() {
if let Ok(apps) = self.db().get_pot_members(pot.to_owned()) { if let Ok(apps) = self.db().pot_memberships().get_members(pot.to_owned()) {
for app in apps { for app in apps {
self.state self.state
.emit_app_event(&app, AppEvent::ElementUpdate { id: id.clone() }) .emit_app_event(&app, AppEvent::ElementUpdate { id: id.clone() })
@ -72,17 +74,19 @@ impl CommState {
pub fn remove_element(&self, id: ElementId) -> anyhow::Result<()> { pub fn remove_element(&self, id: ElementId) -> anyhow::Result<()> {
self.db() self.db()
.remove_element(id.clone()) .elements()
.remove(id.clone())
.inspect(|_| debug!("Removed element {{{}}}", &id.to_string())) .inspect(|_| debug!("Removed element {{{}}}", &id.to_string()))
} }
pub fn get_element(&self, id: ElementId) -> Option<Element> { pub fn get_element(&self, id: ElementId) -> Option<Element> {
self.db().get_element(id) self.db().elements().get(id)
} }
pub fn set_peer(&self, peer: Peer) -> anyhow::Result<()> { pub fn set_peer(&self, peer: Peer) -> anyhow::Result<()> {
self.db() self.db()
.add_peer(peer.clone()) .peers()
.add(peer.clone())
.inspect(|_| debug!("Added peer {:?}.", peer)) .inspect(|_| debug!("Added peer {:?}.", peer))
} }
@ -91,7 +95,7 @@ impl CommState {
} }
pub fn add_pot(&self, id: PotId, app_type: String) -> anyhow::Result<()> { pub fn add_pot(&self, id: PotId, app_type: String) -> anyhow::Result<()> {
self.db().add_pot(id.clone(), app_type.clone())?; self.db().pots().add(id.clone(), app_type.clone())?;
let _ = self.state.emit_node_event(UbisyncNodeEvent::NewPot { let _ = self.state.emit_node_event(UbisyncNodeEvent::NewPot {
id: id, id: id,
@ -108,7 +112,7 @@ impl CommState {
} }
pub fn remove_peer_from_family(&self, peer: PeerId) { pub fn remove_peer_from_family(&self, peer: PeerId) {
let _ = self.db().remove_peer_from_family(peer); let _ = self.db().families().remove_peer(peer);
} }
pub fn has_family_join_request(&self, peer: PeerId) -> bool { pub fn has_family_join_request(&self, peer: PeerId) -> bool {
@ -116,11 +120,11 @@ impl CommState {
} }
pub fn set_own_family(&self, family: Family) -> anyhow::Result<()> { pub fn set_own_family(&self, family: Family) -> anyhow::Result<()> {
self.db().add_peer_family(family) self.db().families().add(family)
} }
pub fn get_family_of_peer(&self, peer: PeerId) -> anyhow::Result<Option<FamilyId>> { pub fn get_family_of_peer(&self, peer: PeerId) -> anyhow::Result<Option<FamilyId>> {
self.db().get_family_of_peer(peer) self.db().families().get_by_peer(peer)
} }
pub fn own_peer_id(&self) -> Option<PeerId> { pub fn own_peer_id(&self) -> Option<PeerId> {

View file

@ -1,5 +1,8 @@
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use bonsaidb::core::schema::{Collection, SerializedCollection}; use bonsaidb::{
core::schema::{Collection, SerializedCollection},
local::Database,
};
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::chrono::{DateTime, Utc}; use serde_with::chrono::{DateTime, Utc};
@ -35,8 +38,17 @@ impl From<DbApp> for App {
} }
} }
impl StateDB { pub(crate) struct Apps<'a> {
pub fn add_app( parent: &'a StateDB,
db: &'a Database,
}
impl<'a> Apps<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(
&self, &self,
id: AppId, id: AppId,
name: String, name: String,
@ -52,48 +64,50 @@ impl StateDB {
description, description,
default_pot: None, default_pot: None,
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn set_default_pot(&self, id: AppId, pot: PotId) -> anyhow::Result<()> { pub fn set_default_pot(&self, id: AppId, pot: PotId) -> anyhow::Result<()> {
DbApp::get(&AsKey::new(id), &self.db) DbApp::get(&AsKey::new(id), self.db)
.unwrap() .unwrap()
.unwrap() .unwrap()
.modify(&self.db, |app| app.contents.default_pot = Some(pot.clone())) .modify(self.db, |app| app.contents.default_pot = Some(pot.clone()))
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_default_pot(&self, id: AppId) -> anyhow::Result<Option<Pot>> { pub fn get_default_pot(&self, id: AppId) -> anyhow::Result<Option<Pot>> {
let pot_id = DbApp::get(&AsKey::new(id), &self.db)? let pot_id = DbApp::get(&AsKey::new(id), self.db)?
.map(|app| app.contents.default_pot) .map(|app| app.contents.default_pot)
.ok_or(Error::msg("App not found"))? .ok_or(Error::msg("App not found"))?
.ok_or(Error::msg("Could not get default pot"))?; .ok_or(Error::msg("Could not get default pot"))?;
self.get_pot(pot_id) self.parent.pots().get(pot_id)
} }
pub fn get_all_apps(&self) -> anyhow::Result<Vec<App>> { pub fn get_all(&self) -> anyhow::Result<Vec<App>> {
Ok(DbApp::all(&self.db) Ok(DbApp::all(self.db)
.query()? .query()?
.iter() .iter()
.map(|app| app.contents.clone().into()) .map(|app| app.contents.clone().into())
.collect_vec()) .collect_vec())
} }
pub fn get_all_app_ids(&self) -> anyhow::Result<Vec<AppId>> { pub fn get_all_ids(&self) -> anyhow::Result<Vec<AppId>> {
Ok(DbApp::all(&self.db) Ok(DbApp::all(self.db)
.query()? .query()?
.iter() .iter()
.map(|app| (*app.contents.id).clone()) .map(|app| (*app.contents.id).clone())
.collect_vec()) .collect_vec())
} }
pub fn app_has_access(&self, app: AppId, element: ElementId) -> anyhow::Result<bool> { pub fn has_access(&self, app: AppId, element: ElementId) -> anyhow::Result<bool> {
if let Some(el) = self.get_element(element) { if let Some(el) = self.parent.elements().get(element) {
Ok(self Ok(self
.get_pot_members( .parent
.pot_memberships()
.get_members(
el.pot() el.pot()
.clone() .clone()
.ok_or(Error::msg("Could not fetch pot members"))?, .ok_or(Error::msg("Could not fetch pot members"))?,
@ -104,8 +118,8 @@ impl StateDB {
} }
} }
pub fn get_app(&self, id: AppId) -> anyhow::Result<Option<App>> { pub fn get(&self, id: AppId) -> anyhow::Result<Option<App>> {
DbApp::get(&AsKey::new(id), &self.db) DbApp::get(&AsKey::new(id), self.db)
.map(|app_option| app_option.map(|app| app.contents.into())) .map(|app_option| app_option.map(|app| app.contents.into()))
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
@ -113,7 +127,7 @@ impl StateDB {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ubisync_lib::types::{AppId, ElementContent, ElementId, ContentUpdateStrategy, Pot, PotId}; use ubisync_lib::types::{AppId, ContentUpdateStrategy, ElementContent, ElementId, Pot, PotId};
use crate::{api::v0::app::App, state::database::StateDB}; use crate::{api::v0::app::App, state::database::StateDB};
@ -121,7 +135,8 @@ mod tests {
fn add_get() { fn add_get() {
let db = StateDB::init(None); let db = StateDB::init(None);
let app_id = AppId::new(); let app_id = AppId::new();
db.add_app( db.apps()
.add(
app_id.clone(), app_id.clone(),
"app name".to_string(), "app name".to_string(),
"description".to_string(), "description".to_string(),
@ -129,7 +144,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let retrieved_app = db.get_app(app_id.clone()).unwrap(); let retrieved_app = db.apps().get(app_id.clone()).unwrap();
match retrieved_app { match retrieved_app {
Some(App { Some(App {
@ -159,7 +174,8 @@ mod tests {
fn get_default_pot() { fn get_default_pot() {
let db = StateDB::init(None); let db = StateDB::init(None);
let app_id = AppId::new(); let app_id = AppId::new();
db.add_app( db.apps()
.add(
app_id.clone(), app_id.clone(),
"app name".to_string(), "app name".to_string(),
"description".to_string(), "description".to_string(),
@ -167,14 +183,15 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(db.get_default_pot(app_id).unwrap(), None) assert_eq!(db.apps().get_default_pot(app_id).unwrap(), None)
} }
#[test] #[test]
fn set_default_pot() { fn set_default_pot() {
let db = StateDB::init(None); let db = StateDB::init(None);
let app_id = AppId::new(); let app_id = AppId::new();
db.add_app( db.apps()
.add(
app_id.clone(), app_id.clone(),
"app name".to_string(), "app name".to_string(),
"description".to_string(), "description".to_string(),
@ -183,28 +200,32 @@ mod tests {
.unwrap(); .unwrap();
let pot = Pot::new(PotId::new(), "app_type".to_string()); let pot = Pot::new(PotId::new(), "app_type".to_string());
db.add_pot(pot.id.clone(), pot.app_type.clone()).unwrap(); db.pots().add(pot.id.clone(), pot.app_type.clone()).unwrap();
db.set_default_pot(app_id.clone(), pot.id.clone()).unwrap(); db.apps()
.set_default_pot(app_id.clone(), pot.id.clone())
.unwrap();
assert_eq!(db.get_default_pot(app_id).unwrap(), Some(pot)) assert_eq!(db.apps().get_default_pot(app_id).unwrap(), Some(pot))
} }
#[test] #[test]
fn get_apps() { fn get_apps() {
let db = StateDB::init(None); let db = StateDB::init(None);
assert_eq!(db.get_all_apps().unwrap(), vec![]); assert_eq!(db.apps().get_all().unwrap(), vec![]);
let (app1, app2) = (AppId::new(), AppId::new()); let (app1, app2) = (AppId::new(), AppId::new());
db.add_app( db.apps()
.add(
app1, app1,
"name1".to_string(), "name1".to_string(),
"desc1".to_string(), "desc1".to_string(),
"type1".to_string(), "type1".to_string(),
) )
.unwrap(); .unwrap();
db.add_app( db.apps()
.add(
app2, app2,
"name2".to_string(), "name2".to_string(),
"desc2".to_string(), "desc2".to_string(),
@ -212,24 +233,26 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(db.get_all_apps().unwrap().len(), 2); assert_eq!(db.apps().get_all().unwrap().len(), 2);
} }
#[test] #[test]
fn get_app_ids() { fn get_app_ids() {
let db = StateDB::init(None); let db = StateDB::init(None);
assert_eq!(db.get_all_app_ids().unwrap(), vec![]); assert_eq!(db.apps().get_all_ids().unwrap(), vec![]);
let (app1, app2) = (AppId::new(), AppId::new()); let (app1, app2) = (AppId::new(), AppId::new());
db.add_app( db.apps()
.add(
app1.clone(), app1.clone(),
"name1".to_string(), "name1".to_string(),
"desc1".to_string(), "desc1".to_string(),
"type1".to_string(), "type1".to_string(),
) )
.unwrap(); .unwrap();
db.add_app( db.apps()
.add(
app2.clone(), app2.clone(),
"name2".to_string(), "name2".to_string(),
"desc2".to_string(), "desc2".to_string(),
@ -237,7 +260,7 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(db.get_all_app_ids().unwrap(), vec![app1, app2]) assert_eq!(db.apps().get_all_ids().unwrap(), vec![app1, app2])
} }
#[test] #[test]
@ -247,7 +270,8 @@ mod tests {
let pot_id = PotId::new(); let pot_id = PotId::new();
let element_id = ElementId::new(); let element_id = ElementId::new();
db.add_app( db.apps()
.add(
app_id.clone(), app_id.clone(),
"name".to_string(), "name".to_string(),
"description".to_string(), "description".to_string(),
@ -255,8 +279,11 @@ mod tests {
) )
.unwrap(); .unwrap();
db.add_pot(pot_id.clone(), "app_type".to_string()).unwrap(); db.pots()
db.add_element( .add(pot_id.clone(), "app_type".to_string())
.unwrap();
db.elements()
.add(
element_id.clone(), element_id.clone(),
ElementContent::Text("Text".to_string()), ElementContent::Text("Text".to_string()),
ContentUpdateStrategy::Overwrite, ContentUpdateStrategy::Overwrite,
@ -267,12 +294,13 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.app_has_access(app_id.clone(), element_id.clone()) db.apps()
.has_access(app_id.clone(), element_id.clone())
.unwrap(), .unwrap(),
false false
); );
db.add_pot_membership(pot_id, app_id.clone()).unwrap(); db.pot_memberships().add(pot_id, app_id.clone()).unwrap();
assert_eq!(db.app_has_access(app_id, element_id).unwrap(), true); assert_eq!(db.apps().has_access(app_id, element_id).unwrap(), true);
} }
} }

View file

@ -1,7 +1,12 @@
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use bonsaidb::core::schema::{Collection, SerializedCollection}; use bonsaidb::{
core::schema::{Collection, SerializedCollection},
local::Database,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ubisync_lib::types::{Element, ElementContent, ElementId, ContentUpdateStrategy, MessageId, PotId}; use ubisync_lib::types::{
ContentUpdateStrategy, Element, ElementContent, ElementId, MessageId, PotId,
};
use crate::state::database::{as_key::AsKey, StateDB}; use crate::state::database::{as_key::AsKey, StateDB};
@ -30,8 +35,16 @@ impl From<DbElement> for Element {
} }
} }
impl StateDB { pub(crate) struct Elements<'a> {
pub fn add_element( parent: &'a StateDB,
db: &'a Database,
}
impl<'a> Elements<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(
&self, &self,
id: ElementId, id: ElementId,
content: ElementContent, content: ElementContent,
@ -49,66 +62,60 @@ impl StateDB {
local_changes, local_changes,
pot, pot,
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_element(&self, id: ElementId) -> Option<Element> { pub fn get(&self, id: ElementId) -> Option<Element> {
DbElement::get(&AsKey::new(id), &self.db) DbElement::get(&AsKey::new(id), self.db)
.ok()? .ok()?
.map(|el| el.contents.into()) .map(|el| el.contents.into())
} }
pub fn set_element_content( pub fn set_content(&self, id: ElementId, content: ElementContent) -> anyhow::Result<()> {
&self, DbElement::get(&AsKey::new(id), self.db)
id: ElementId,
content: ElementContent,
) -> anyhow::Result<()> {
DbElement::get(&AsKey::new(id), &self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find element by id"))? .ok_or(Error::msg("Could not find element by id"))?
.modify(&self.db, |t| t.contents.content = content.clone()) .modify(self.db, |t| t.contents.content = content.clone())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn set_element_latest_message( pub fn set_latest_message(
&self, &self,
id: ElementId, id: ElementId,
message: Option<MessageId>, message: Option<MessageId>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
DbElement::get(&AsKey::new(id), &self.db) DbElement::get(&AsKey::new(id), self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find element by id"))? .ok_or(Error::msg("Could not find element by id"))?
.modify(&self.db, |t| t.contents.latest_message = message.clone()) .modify(self.db, |t| t.contents.latest_message = message.clone())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn set_element_local_changes( pub fn set_local_changes(&self, id: ElementId, local_changes: bool) -> anyhow::Result<()> {
&self, DbElement::get(&AsKey::new(id), self.db)
id: ElementId,
local_changes: bool,
) -> anyhow::Result<()> {
DbElement::get(&AsKey::new(id), &self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find element by id"))? .ok_or(Error::msg("Could not find element by id"))?
.modify(&self.db, |t| t.contents.local_changes = local_changes) .modify(self.db, |t| t.contents.local_changes = local_changes)
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn remove_element(&self, id: ElementId) -> anyhow::Result<()> { pub fn remove(&self, id: ElementId) -> anyhow::Result<()> {
DbElement::get(&AsKey::new(id), &self.db) DbElement::get(&AsKey::new(id), self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find element by id"))? .ok_or(Error::msg("Could not find element by id"))?
.delete(&self.db) .delete(self.db)
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ubisync_lib::types::{Element, ElementContent, ElementId, ContentUpdateStrategy, MessageId, PotId}; use ubisync_lib::types::{
ContentUpdateStrategy, Element, ElementContent, ElementId, MessageId, PotId,
};
use crate::state::database::StateDB; use crate::state::database::StateDB;
@ -117,7 +124,8 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let pot_id = PotId::new(); let pot_id = PotId::new();
let element_id = ElementId::new(); let element_id = ElementId::new();
db.add_element( db.elements()
.add(
element_id.clone(), element_id.clone(),
ElementContent::Text("Content!!!".to_string()), ElementContent::Text("Content!!!".to_string()),
ContentUpdateStrategy::default(), ContentUpdateStrategy::default(),
@ -127,19 +135,17 @@ mod tests {
) )
.unwrap(); .unwrap();
let retrieved_element = db.get_element(element_id.clone()); let retrieved_element = db.elements().get(element_id.clone());
assert_eq!( assert_eq!(
Some( Some(Element {
Element {
id: element_id, id: element_id,
content: ElementContent::Text("Content!!!".to_string()), content: ElementContent::Text("Content!!!".to_string()),
update_strategy: ContentUpdateStrategy::default(), update_strategy: ContentUpdateStrategy::default(),
latest_message: None, latest_message: None,
local_changes: false, local_changes: false,
pot: Some(pot_id), pot: Some(pot_id),
} }),
),
retrieved_element retrieved_element
) )
} }
@ -148,7 +154,8 @@ mod tests {
fn set_content() { fn set_content() {
let db = StateDB::init(None); let db = StateDB::init(None);
let element_id = ElementId::new(); let element_id = ElementId::new();
db.add_element( db.elements()
.add(
element_id.clone(), element_id.clone(),
ElementContent::Text("Content!!!".to_string()), ElementContent::Text("Content!!!".to_string()),
ContentUpdateStrategy::default(), ContentUpdateStrategy::default(),
@ -157,14 +164,15 @@ mod tests {
PotId::new(), PotId::new(),
) )
.unwrap(); .unwrap();
db.set_element_content( db.elements()
.set_content(
element_id.clone(), element_id.clone(),
ElementContent::Text("New Content!!!".to_string()), ElementContent::Text("New Content!!!".to_string()),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_element(element_id).unwrap().content().to_owned(), db.elements().get(element_id).unwrap().content().to_owned(),
ElementContent::Text("New Content!!!".to_string()) ElementContent::Text("New Content!!!".to_string())
) )
} }
@ -174,7 +182,8 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let element_id = ElementId::new(); let element_id = ElementId::new();
db.add_element( db.elements()
.add(
element_id.clone(), element_id.clone(),
ElementContent::Text("Content!!!".to_string()), ElementContent::Text("Content!!!".to_string()),
ContentUpdateStrategy::default(), ContentUpdateStrategy::default(),
@ -185,7 +194,8 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_element(element_id.clone()) db.elements()
.get(element_id.clone())
.unwrap() .unwrap()
.latest_message() .latest_message()
.to_owned(), .to_owned(),
@ -193,11 +203,13 @@ mod tests {
); );
let msg_id = MessageId::new(); let msg_id = MessageId::new();
db.set_element_latest_message(element_id.clone(), Some(msg_id.clone())) db.elements()
.set_latest_message(element_id.clone(), Some(msg_id.clone()))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_element(element_id) db.elements()
.get(element_id)
.unwrap() .unwrap()
.latest_message() .latest_message()
.to_owned(), .to_owned(),
@ -210,7 +222,8 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let element_id = ElementId::new(); let element_id = ElementId::new();
db.add_element( db.elements()
.add(
element_id.clone(), element_id.clone(),
ElementContent::Text("Content!!!".to_string()), ElementContent::Text("Content!!!".to_string()),
ContentUpdateStrategy::default(), ContentUpdateStrategy::default(),
@ -221,20 +234,17 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_element(element_id.clone()) db.elements()
.unwrap().local_changes(), .get(element_id.clone())
.unwrap()
.local_changes(),
false false
); );
db.set_element_local_changes(element_id.clone(), true) db.elements()
.set_local_changes(element_id.clone(), true)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(db.elements().get(element_id).unwrap().local_changes(), true)
db.get_element(element_id)
.unwrap()
.local_changes(),
true
)
} }
} }

View file

@ -7,6 +7,13 @@ use peers::DbPeer;
use pot_memberships::DbPotMembership; use pot_memberships::DbPotMembership;
use pots::DbPot; use pots::DbPot;
pub(crate) use apps::Apps;
pub(crate) use elements::Elements;
pub(crate) use peer_families::Families;
pub(crate) use peers::Peers;
pub(crate) use pot_memberships::PotMemberships;
pub(crate) use pots::Pots;
mod apps; mod apps;
mod elements; mod elements;
mod peer_families; mod peer_families;
@ -20,4 +27,3 @@ pub struct UbisyncSchema;
#[cfg(test)] #[cfg(test)]
mod tests {} mod tests {}

View file

@ -1,10 +1,15 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use bonsaidb::core::{ use bonsaidb::{
core::{
connection::Connection, connection::Connection,
document::Emit, document::Emit,
schema::{view::map::Mappings, Collection, MapReduce, SerializedCollection, View, ViewSchema}, schema::{
view::map::Mappings, Collection, MapReduce, SerializedCollection, View, ViewSchema,
},
},
local::Database,
}; };
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -85,9 +90,17 @@ impl From<DbPeerFamily> for Family {
} }
} }
impl StateDB { pub(crate) struct Families<'a> {
pub fn add_peer_family(&self, family: Family) -> anyhow::Result<()> { parent: &'a StateDB,
if self.get_peer_family(family.id.clone())?.is_some() { db: &'a Database,
}
impl<'a> Families<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(&self, family: Family) -> anyhow::Result<()> {
if self.get(family.id.clone())?.is_some() {
Err(Error::msg("Peer family already exists")) Err(Error::msg("Peer family already exists"))
} else { } else {
DbPeerFamily::push( DbPeerFamily::push(
@ -96,44 +109,40 @@ impl StateDB {
name: family.name, name: family.name,
members: HashSet::from_iter(family.members), members: HashSet::from_iter(family.members),
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
} }
pub fn add_peer_to_family(&self, peer: PeerId, family: FamilyId) -> anyhow::Result<()> { pub fn add_peer(&self, peer: PeerId, family: FamilyId) -> anyhow::Result<()> {
DbPeerFamily::get(&AsKey::new(family), &self.db) DbPeerFamily::get(&AsKey::new(family), self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find peer family"))? .ok_or(Error::msg("Could not find peer family"))?
.modify(&self.db, |doc| { .modify(self.db, |doc| {
doc.contents.members.insert(peer.clone()); doc.contents.members.insert(peer.clone());
}) })
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn set_peer_family_name( pub fn set_name(&self, family: FamilyId, name: Option<String>) -> anyhow::Result<()> {
&self, DbPeerFamily::get(&AsKey::new(family), self.db)
family: FamilyId,
name: Option<String>,
) -> anyhow::Result<()> {
DbPeerFamily::get(&AsKey::new(family), &self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Could not find peer family"))? .ok_or(Error::msg("Could not find peer family"))?
.modify(&self.db, |doc| { .modify(self.db, |doc| {
doc.contents.name = name.clone(); doc.contents.name = name.clone();
}) })
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_peer_family(&self, id: FamilyId) -> anyhow::Result<Option<Family>> { pub fn get(&self, id: FamilyId) -> anyhow::Result<Option<Family>> {
DbPeerFamily::get(&AsKey::new(id), &self.db) DbPeerFamily::get(&AsKey::new(id), self.db)
.map(|doc_opt| doc_opt.map(|doc| doc.contents.into())) .map(|doc_opt| doc_opt.map(|doc| doc.contents.into()))
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_family_of_peer(&self, peer: PeerId) -> anyhow::Result<Option<FamilyId>> { pub fn get_by_peer(&self, peer: PeerId) -> anyhow::Result<Option<FamilyId>> {
self.db self.db
.view::<DbPeerFamilyIdByMemberId>() .view::<DbPeerFamilyIdByMemberId>()
.with_key(&AsKey::new(peer)) .with_key(&AsKey::new(peer))
@ -146,7 +155,7 @@ impl StateDB {
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_peer_family_members(&self, peer: PeerId) -> anyhow::Result<Vec<PeerId>> { pub fn get_members(&self, peer: PeerId) -> anyhow::Result<Vec<PeerId>> {
self.db self.db
.view::<DbPeerFamilyByMemberId>() .view::<DbPeerFamilyByMemberId>()
.with_key(&AsKey::new(peer)) .with_key(&AsKey::new(peer))
@ -160,20 +169,21 @@ impl StateDB {
.value .value
.clone() .clone()
.map(|family| family.members) .map(|family| family.members)
.map(|map| map.into_iter().collect_vec()).unwrap_or(vec![])), .map(|map| map.into_iter().collect_vec())
.unwrap_or(vec![])),
_ => Err(Error::msg("Peer appears to be member of multiple families")), _ => Err(Error::msg("Peer appears to be member of multiple families")),
})? })?
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn remove_peer_from_family(&self, peer: PeerId) -> anyhow::Result<()> { pub fn remove_peer(&self, peer: PeerId) -> anyhow::Result<()> {
self.db self.db
.view::<DbPeerFamilyIdByMemberId>() .view::<DbPeerFamilyIdByMemberId>()
.with_key(&AsKey::new(peer.clone())) .with_key(&AsKey::new(peer.clone()))
.query_with_collection_docs() .query_with_collection_docs()
.map(|results| { .map(|results| {
if let Some(family) = results.into_iter().next() { if let Some(family) = results.into_iter().next() {
family.document.to_owned().modify(&self.db, |doc| { family.document.to_owned().modify(self.db, |doc| {
doc.contents.members.remove(&peer.clone()); doc.contents.members.remove(&peer.clone());
}) })
} else { } else {
@ -199,13 +209,14 @@ mod tests {
let family_id = FamilyId::new(); let family_id = FamilyId::new();
let peer_id = PeerId::default(); let peer_id = PeerId::default();
db.add_peer_family(Family { db.families()
.add(Family {
id: family_id.clone(), id: family_id.clone(),
name: Some("My family name".to_string()), name: Some("My family name".to_string()),
members: HashSet::from([peer_id.clone()]), members: HashSet::from([peer_id.clone()]),
}) })
.unwrap(); .unwrap();
let retrieved_family = db.get_peer_family(family_id.clone()).unwrap(); let retrieved_family = db.families().get(family_id.clone()).unwrap();
assert_eq!( assert_eq!(
retrieved_family, retrieved_family,
@ -223,14 +234,15 @@ mod tests {
let family_id = FamilyId::new(); let family_id = FamilyId::new();
let peer_id = PeerId::default(); let peer_id = PeerId::default();
db.add_peer_family(Family { db.families()
.add(Family {
id: family_id.clone(), id: family_id.clone(),
name: Some("My family name".to_string()), name: Some("My family name".to_string()),
members: HashSet::from([peer_id.clone()]), members: HashSet::from([peer_id.clone()]),
}) })
.unwrap(); .unwrap();
db.remove_peer_from_family(peer_id.clone()).unwrap(); db.families().remove_peer(peer_id.clone()).unwrap();
let retrieved_family = db.get_peer_family(family_id.clone()).unwrap(); let retrieved_family = db.families().get(family_id.clone()).unwrap();
assert_eq!( assert_eq!(
retrieved_family, retrieved_family,
@ -248,7 +260,8 @@ mod tests {
let family_id = FamilyId::new(); let family_id = FamilyId::new();
let peer_id = PeerId::default(); let peer_id = PeerId::default();
db.add_peer_family(Family { db.families()
.add(Family {
id: family_id.clone(), id: family_id.clone(),
name: Some("My family name".to_string()), name: Some("My family name".to_string()),
members: HashSet::from([peer_id.clone()]), members: HashSet::from([peer_id.clone()]),
@ -256,7 +269,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_peer_family(family_id.clone()).unwrap(), db.families().get(family_id.clone()).unwrap(),
Some(Family::new( Some(Family::new(
family_id.clone(), family_id.clone(),
Some("My family name".to_string()), Some("My family name".to_string()),
@ -264,11 +277,12 @@ mod tests {
)) ))
); );
db.set_peer_family_name(family_id.clone(), Some("New family name".to_string())) db.families()
.set_name(family_id.clone(), Some("New family name".to_string()))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
db.get_peer_family(family_id.clone()).unwrap(), db.families().get(family_id.clone()).unwrap(),
Some(Family::new( Some(Family::new(
family_id, family_id,
Some("New family name".to_string()), Some("New family name".to_string()),
@ -278,18 +292,19 @@ mod tests {
} }
#[test] #[test]
fn get_family_of_peer() { fn get_by_peer() {
let db = StateDB::init(None); let db = StateDB::init(None);
let family_id = FamilyId::new(); let family_id = FamilyId::new();
let peer_id = PeerId::default(); let peer_id = PeerId::default();
db.add_peer_family(Family { db.families()
.add(Family {
id: family_id.clone(), id: family_id.clone(),
name: Some("My family name".to_string()), name: Some("My family name".to_string()),
members: HashSet::from([peer_id.clone()]), members: HashSet::from([peer_id.clone()]),
}) })
.unwrap(); .unwrap();
assert_eq!(db.get_family_of_peer(peer_id).unwrap(), Some(family_id)) assert_eq!(db.families().get_by_peer(peer_id).unwrap(), Some(family_id))
} }
} }

View file

@ -1,16 +1,14 @@
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use bonsaidb::core::schema::{Collection, SerializedCollection}; use bonsaidb::{
core::schema::{Collection, SerializedCollection},
local::Database,
};
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ubisync_lib::{ use ubisync_lib::{peer::Peer, types::PeerId};
peer::Peer,
types::{ElementId, PeerId},
};
use crate::state::database::{as_key::AsKey, StateDB}; use crate::state::database::{as_key::AsKey, StateDB};
use super::peer_families::DbPeerFamily;
#[derive(Debug, Serialize, Deserialize, Collection, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, Collection, PartialEq, Clone)]
#[collection(name = "peers", views = [])] #[collection(name = "peers", views = [])]
pub(super) struct DbPeer { pub(super) struct DbPeer {
@ -25,9 +23,18 @@ impl From<DbPeer> for Peer {
} }
} }
impl StateDB { pub(crate) struct Peers<'a> {
pub fn add_peer(&self, peer: Peer) -> anyhow::Result<()> { parent: &'a StateDB,
if self.get_peer(peer.id())?.is_some() { db: &'a Database,
}
impl<'a> Peers<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(&self, peer: Peer) -> anyhow::Result<()> {
if self.get(peer.id())?.is_some() {
Err(Error::msg("Peer already exists")) Err(Error::msg("Peer already exists"))
} else { } else {
DbPeer::push( DbPeer::push(
@ -35,21 +42,21 @@ impl StateDB {
id: AsKey::new(peer.id()), id: AsKey::new(peer.id()),
name: peer.name(), name: peer.name(),
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
} }
pub fn get_peer(&self, id: PeerId) -> anyhow::Result<Option<Peer>> { pub fn get(&self, id: PeerId) -> anyhow::Result<Option<Peer>> {
DbPeer::get(&AsKey::new(id), &self.db) DbPeer::get(&AsKey::new(id), self.db)
.map(|doc| doc.map(|peer| Peer::new((*peer.contents.id).clone(), peer.contents.name))) .map(|doc| doc.map(|peer| Peer::new((*peer.contents.id).clone(), peer.contents.name)))
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_all_peers(&self) -> anyhow::Result<Vec<Peer>> { pub fn get_all(&self) -> anyhow::Result<Vec<Peer>> {
DbPeer::all(&self.db) DbPeer::all(self.db)
.query() .query()
.map(|peers| { .map(|peers| {
peers peers
@ -60,11 +67,11 @@ impl StateDB {
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn set_peer_name(&self, id: PeerId, name: &Option<String>) -> anyhow::Result<()> { pub fn set_name(&self, id: PeerId, name: &Option<String>) -> anyhow::Result<()> {
DbPeer::get(&AsKey::new(id), &self.db) DbPeer::get(&AsKey::new(id), self.db)
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
.ok_or(Error::msg("Peer not found"))? .ok_or(Error::msg("Peer not found"))?
.modify(&self.db, |doc| doc.contents.name = name.clone()) .modify(self.db, |doc| doc.contents.name = name.clone())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
} }
@ -80,9 +87,9 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let peer = Peer::new(PeerId::default(), Some("Peer name".to_string())); let peer = Peer::new(PeerId::default(), Some("Peer name".to_string()));
db.add_peer(peer.clone()).unwrap(); db.peers().add(peer.clone()).unwrap();
let retrieved_peer = db.get_peer(peer.id()).unwrap(); let retrieved_peer = db.peers().get(peer.id()).unwrap();
assert_eq!(Some(peer), retrieved_peer) assert_eq!(Some(peer), retrieved_peer)
} }
@ -92,9 +99,9 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let peer = Peer::new(PeerId::default(), Some("Peer name".to_string())); let peer = Peer::new(PeerId::default(), Some("Peer name".to_string()));
db.add_peer(peer.clone()).unwrap(); db.peers().add(peer.clone()).unwrap();
let all_peers = db.get_all_peers().unwrap(); let all_peers = db.peers().get_all().unwrap();
assert_eq!(all_peers, vec![peer]); assert_eq!(all_peers, vec![peer]);
} }
@ -103,11 +110,12 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let peer = Peer::new(PeerId::default(), Some("Peer name".to_string())); let peer = Peer::new(PeerId::default(), Some("Peer name".to_string()));
db.add_peer(peer.clone()).unwrap(); db.peers().add(peer.clone()).unwrap();
db.set_peer_name(peer.id().clone(), &Some("New peer name".to_string())) db.peers()
.set_name(peer.id().clone(), &Some("New peer name".to_string()))
.unwrap(); .unwrap();
let retrieved_peer = db.get_peer(peer.id()).unwrap(); let retrieved_peer = db.peers().get(peer.id()).unwrap();
assert_eq!( assert_eq!(
Some(Peer::new(peer.id(), Some("New peer name".to_string()))), Some(Peer::new(peer.id(), Some("New peer name".to_string()))),

View file

@ -1,11 +1,15 @@
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use bonsaidb::core::{ use bonsaidb::{
core::{
connection::Connection, connection::Connection,
document::Emit, document::Emit,
schema::{Collection, MapReduce, SerializedCollection, View, ViewSchema}, schema::{Collection, MapReduce, SerializedCollection, View, ViewSchema},
},
local::Database,
}; };
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::debug;
use ubisync_lib::types::{AppId, PotId}; use ubisync_lib::types::{AppId, PotId};
use crate::state::database::{as_key::AsKey, StateDB}; use crate::state::database::{as_key::AsKey, StateDB};
@ -93,18 +97,26 @@ impl MapReduce for DbPotMembershipsByBothIds {
} }
} }
impl StateDB { pub(crate) struct PotMemberships<'a> {
pub fn add_pot_membership(&self, pot: PotId, app: AppId) -> anyhow::Result<()> { parent: &'a StateDB,
if let Err(_) = self.get_pot(pot.clone()) { db: &'a Database,
}
impl<'a> PotMemberships<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(&self, pot: PotId, app: AppId) -> anyhow::Result<()> {
if let Err(_) = self.parent.pots().get(pot.clone()) {
Err(Error::msg( Err(Error::msg(
"A member was meant to be added to a pot which does not exist.", "A member was meant to be added to a pot which does not exist.",
)) ))
} else if let Err(_) = self.get_app(app.clone()) { } else if let Err(_) = self.parent.apps().get(app.clone()) {
Err(Error::msg( Err(Error::msg(
"A member app which does not exist was meant to be added to a pot", "A member app which does not exist was meant to be added to a pot",
)) ))
} else { } else {
if self.get_pot_membership(pot.clone(), app.clone())?.is_some() { if self.get(pot.clone(), app.clone())?.is_some() {
Err(Error::msg("Pot membership already exists")) Err(Error::msg("Pot membership already exists"))
} else { } else {
DbPotMembership::push( DbPotMembership::push(
@ -112,7 +124,7 @@ impl StateDB {
pot_id: AsKey::new(pot), pot_id: AsKey::new(pot),
app_id: AsKey::new(app), app_id: AsKey::new(app),
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
@ -120,7 +132,7 @@ impl StateDB {
} }
} }
pub fn get_pot_membership(&self, pot: PotId, app: AppId) -> anyhow::Result<Option<()>> { pub fn get(&self, pot: PotId, app: AppId) -> anyhow::Result<Option<()>> {
self.db self.db
.view::<DbPotMembershipsByBothIds>() .view::<DbPotMembershipsByBothIds>()
.with_key(&(AsKey::new(pot), AsKey::new(app))) .with_key(&(AsKey::new(pot), AsKey::new(app)))
@ -129,7 +141,7 @@ impl StateDB {
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_pot_members(&self, pot: PotId) -> anyhow::Result<Vec<AppId>> { pub fn get_members(&self, pot: PotId) -> anyhow::Result<Vec<AppId>> {
self.db self.db
.view::<DbPotMembershipsByPotId>() .view::<DbPotMembershipsByPotId>()
.with_key(&AsKey::new(pot)) .with_key(&AsKey::new(pot))
@ -137,7 +149,7 @@ impl StateDB {
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub fn get_app_pot_memberships(&self, app: AppId) -> anyhow::Result<Vec<PotId>> { pub fn get_memberships(&self, app: AppId) -> anyhow::Result<Vec<PotId>> {
self.db self.db
.view::<DbPotMembershipsByAppId>() .view::<DbPotMembershipsByAppId>()
.with_key(&AsKey::new(app)) .with_key(&AsKey::new(app))
@ -157,21 +169,25 @@ mod tests {
let db = StateDB::init(None); let db = StateDB::init(None);
let pot_id = PotId::new(); let pot_id = PotId::new();
let app_id = AppId::new(); let app_id = AppId::new();
db.add_pot(pot_id.clone(), "app_type".to_string()).unwrap(); db.pots()
db.add_app( .add(pot_id.clone(), "app_type".to_string())
.unwrap();
db.apps()
.add(
app_id.clone(), app_id.clone(),
"name".to_string(), "name".to_string(),
"description".to_string(), "description".to_string(),
"app_type".to_string(), "app_type".to_string(),
) )
.unwrap(); .unwrap();
db.add_pot_membership(pot_id.clone(), app_id.clone()) db.pot_memberships()
.add(pot_id.clone(), app_id.clone())
.unwrap(); .unwrap();
let retrieved_members = db.get_pot_members(pot_id.clone()).unwrap(); let retrieved_members = db.pot_memberships().get_members(pot_id.clone()).unwrap();
assert_eq!(vec![app_id.clone()], retrieved_members); assert_eq!(vec![app_id.clone()], retrieved_members);
let retrieved_memberships = db.get_app_pot_memberships(app_id).unwrap(); let retrieved_memberships = db.pot_memberships().get_memberships(app_id).unwrap();
assert_eq!(vec![pot_id], retrieved_memberships) assert_eq!(vec![pot_id], retrieved_memberships)
} }
} }

View file

@ -1,5 +1,8 @@
use anyhow::{Error, anyhow}; use anyhow::{anyhow, Error};
use bonsaidb::core::schema::{Collection, SerializedCollection}; use bonsaidb::{
core::schema::{Collection, SerializedCollection},
local::Database,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ubisync_lib::types::{Pot, PotId}; use ubisync_lib::types::{Pot, PotId};
@ -22,9 +25,17 @@ impl From<DbPot> for Pot {
} }
} }
impl StateDB { pub(crate) struct Pots<'a> {
pub fn add_pot(&self, id: PotId, app_type: String) -> anyhow::Result<()> { parent: &'a StateDB,
if self.get_pot(id.clone())?.is_some() { db: &'a Database,
}
impl<'a> Pots<'a> {
pub const fn new(parent: &'a StateDB, bonsai: &'a Database) -> Self {
Self { parent, db: bonsai }
}
pub fn add(&self, id: PotId, app_type: String) -> anyhow::Result<()> {
if self.get(id.clone())?.is_some() {
Err(Error::msg("Pot already exists")) Err(Error::msg("Pot already exists"))
} else { } else {
DbPot::push( DbPot::push(
@ -32,15 +43,15 @@ impl StateDB {
id: AsKey::new(id), id: AsKey::new(id),
app_type, app_type,
}, },
&self.db, self.db,
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
} }
pub fn get_pot(&self, id: PotId) -> anyhow::Result<Option<Pot>> { pub fn get(&self, id: PotId) -> anyhow::Result<Option<Pot>> {
DbPot::get(&AsKey::new(id), &self.db) DbPot::get(&AsKey::new(id), self.db)
.map(|pot_opt| pot_opt.map(|pot| pot.contents.into())) .map(|pot_opt| pot_opt.map(|pot| pot.contents.into()))
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
@ -56,9 +67,11 @@ mod tests {
fn add_get() { fn add_get() {
let db = StateDB::init(None); let db = StateDB::init(None);
let pot_id = PotId::new(); let pot_id = PotId::new();
db.add_pot(pot_id.clone(), "app_type".to_string()).unwrap(); db.pots()
.add(pot_id.clone(), "app_type".to_string())
.unwrap();
let retrieved_pot = db.get_pot(pot_id.clone()).unwrap(); let retrieved_pot = db.pots().get(pot_id.clone()).unwrap();
assert_eq!( assert_eq!(
retrieved_pot, retrieved_pot,
Some(Pot { Some(Pot {

View file

@ -2,21 +2,21 @@ use anyhow::anyhow;
use bonsaidb::{ use bonsaidb::{
core::keyvalue::KeyValue, core::keyvalue::KeyValue,
local::{ local::{
config::{Builder, StorageConfiguration}, config::{Builder, StorageConfiguration},Database
Database as BonsaiDb,
}, },
}; };
use tracing::debug; use tracing::debug;
use ubisync_lib::types::PeerId; use ubisync_lib::types::PeerId;
use uuid::Uuid; use uuid::Uuid;
use self::collections::UbisyncSchema; use self::collections::{Apps, Elements, Families, Peers, PotMemberships, Pots, UbisyncSchema};
mod as_key; mod as_key;
mod collections; mod collections;
pub struct StateDB { pub struct StateDB {
db: BonsaiDb, db: Database,
} }
impl StateDB { impl StateDB {
@ -26,9 +26,8 @@ impl StateDB {
None => StorageConfiguration::new(format!("/tmp/{}", Uuid::new_v4())), None => StorageConfiguration::new(format!("/tmp/{}", Uuid::new_v4())),
// None => StorageConfiguration::default().memory_only() // None => StorageConfiguration::default().memory_only()
}; };
StateDB { let db = Database::open::<UbisyncSchema>(storage_conf).unwrap();
db: BonsaiDb::open::<UbisyncSchema>(storage_conf).unwrap(), StateDB { db }
}
} }
pub fn add_family_join_request(&self, peer: PeerId) { pub fn add_family_join_request(&self, peer: PeerId) {
@ -54,6 +53,25 @@ impl StateDB {
.map(|_| ()) .map(|_| ())
.map_err(|e| anyhow!(e)) .map_err(|e| anyhow!(e))
} }
pub const fn apps(&self) -> Apps {
Apps::new(&self, &self.db)
}
pub const fn elements(&self) -> Elements {
Elements::new(&self, &self.db)
}
pub const fn families(&self) -> Families {
Families::new(&self, &self.db)
}
pub const fn peers(&self) -> Peers {
Peers::new(&self, &self.db)
}
pub const fn pot_memberships(&self) -> PotMemberships {
PotMemberships::new(&self, &self.db)
}
pub const fn pots(&self) -> Pots {
Pots::new(&self, &self.db)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -76,7 +76,7 @@ impl State {
} }
pub fn get_apps(&self) -> anyhow::Result<Vec<App>> { pub fn get_apps(&self) -> anyhow::Result<Vec<App>> {
self.db.get_all_apps() self.db.apps().get_all()
} }
pub fn set_element_content( pub fn set_element_content(
@ -85,7 +85,8 @@ impl State {
content: ElementContent, content: ElementContent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.db self.db
.set_element_content(element_id.clone(), content.clone()) .elements()
.set_content(element_id.clone(), content.clone())
.inspect(|_| { .inspect(|_| {
//TODO: Get all peers interested in the element, e.g. because they subscribe to the element's pot, a share, etc. //TODO: Get all peers interested in the element, e.g. because they subscribe to the element's pot, a share, etc.
self.send_to_peers( self.send_to_peers(
@ -94,28 +95,28 @@ impl State {
content: content.clone(), content: content.clone(),
}, },
self.own_peer_id() self.own_peer_id()
.map(|id| self.db.get_peer_family_members(id).unwrap_or_default()) .map(|id| self.db.families().get_members(id).unwrap_or_default())
.unwrap_or_default(), .unwrap_or_default(),
) )
}) })
} }
pub fn remove_element(&self, element_id: ElementId) -> anyhow::Result<()> { pub fn remove_element(&self, element_id: ElementId) -> anyhow::Result<()> {
self.db.remove_element(element_id.clone()).inspect(|_| { self.db.elements().remove(element_id.clone()).inspect(|_| {
//TODO: Get all peers interested in the element, e.g. because they subscribe to the element's pot, a share, etc. //TODO: Get all peers interested in the element, e.g. because they subscribe to the element's pot, a share, etc.
self.send_to_peers( self.send_to_peers(
MessageContent::RemoveElement { MessageContent::RemoveElement {
id: element_id.clone(), id: element_id.clone(),
}, },
self.own_peer_id() self.own_peer_id()
.map(|id| self.db.get_peer_family_members(id).unwrap_or_default()) .map(|id| self.db.families().get_members(id).unwrap_or_default())
.unwrap_or_default(), .unwrap_or_default(),
) )
}) })
} }
pub fn get_element(&self, id: ElementId) -> Option<Element> { pub fn get_element(&self, id: ElementId) -> Option<Element> {
self.db.get_element(id) self.db.elements().get(id)
} }
pub fn get_elements_by_tag(&self, _tag: &Tag) -> Vec<ElementId> { pub fn get_elements_by_tag(&self, _tag: &Tag) -> Vec<ElementId> {
@ -123,20 +124,22 @@ impl State {
} }
pub fn add_peer(&self, peer: Peer) -> anyhow::Result<()> { pub fn add_peer(&self, peer: Peer) -> anyhow::Result<()> {
self.db.add_peer(peer) self.db.peers().add(peer)
} }
pub fn get_peers(&self) -> anyhow::Result<Vec<Peer>> { pub fn get_peers(&self) -> anyhow::Result<Vec<Peer>> {
self.db.get_all_peers() self.db.peers().get_all()
} }
pub fn add_pot_member(&self, pot: PotId, app: AppId) -> anyhow::Result<()> { pub fn add_pot_member(&self, pot: PotId, app: AppId) -> anyhow::Result<()> {
let p = self let p = self
.db .db
.get_pot(pot.clone())? .pots()
.get(pot.clone())?
.ok_or(Error::msg("Could not find pot"))?; .ok_or(Error::msg("Could not find pot"))?;
self.db self.db
.add_pot_membership(pot.clone(), app.clone()) .pot_memberships()
.add(pot.clone(), app.clone())
.inspect(|_| { .inspect(|_| {
self.emit_app_event( self.emit_app_event(
&app, &app,
@ -153,11 +156,12 @@ impl State {
.own_peer_id() .own_peer_id()
.ok_or(Error::msg("Could not get own PeerId"))?; .ok_or(Error::msg("Could not get own PeerId"))?;
let my_family = match self.db.get_family_of_peer(my_id.clone())? { let my_family = match self.db.families().get_by_peer(my_id.clone())? {
Some(id) => { Some(id) => {
self.db.add_peer_to_family(peer.clone(), id.clone())?; self.db.families().add_peer(peer.clone(), id.clone())?;
self.db self.db
.get_peer_family(id)? .families()
.get(id)?
.ok_or(Error::msg("Family not found"))? .ok_or(Error::msg("Family not found"))?
} }
None => { None => {
@ -169,7 +173,7 @@ impl State {
name: None, name: None,
members: HashSet::from([my_id.clone(), peer.clone()]), members: HashSet::from([my_id.clone(), peer.clone()]),
}; };
self.db.add_peer_family(family.clone())?; self.db.families().add(family.clone())?;
family family
} }
}; };
@ -186,9 +190,10 @@ impl State {
} }
pub fn get_family_of(&self, peer: PeerId) -> anyhow::Result<Family> { pub fn get_family_of(&self, peer: PeerId) -> anyhow::Result<Family> {
match self.db.get_peer_family( match self.db.families().get(
self.db self.db
.get_family_of_peer(peer)? .families()
.get_by_peer(peer)?
.ok_or(Error::msg("Family of peer not found"))?, .ok_or(Error::msg("Family of peer not found"))?,
) { ) {
Ok(Some(family)) => Ok(family), Ok(Some(family)) => Ok(family),
@ -256,7 +261,7 @@ impl State {
} }
fn init_event_channels(&self) { fn init_event_channels(&self) {
let app_ids = self.db.get_all_app_ids().unwrap(); let app_ids = self.db.apps().get_all_ids().unwrap();
for app in app_ids { for app in app_ids {
self.create_event_channel_if_not_exists(app); self.create_event_channel_if_not_exists(app);
} }