aboutsummaryrefslogtreecommitdiff
path: root/base/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-01-29 18:03:06 +0100
committermetamuffin <metamuffin@disroot.org>2025-01-29 18:03:06 +0100
commitdb511d3fe50f05329615f718515fab1b80d9e06a (patch)
tree7969fea01be100cbe4385ad13a14940a987ac513 /base/src
parent82e8a55a1496ae9132e13e7286fe1c0d57d586d3 (diff)
downloadjellything-db511d3fe50f05329615f718515fab1b80d9e06a.tar
jellything-db511d3fe50f05329615f718515fab1b80d9e06a.tar.bz2
jellything-db511d3fe50f05329615f718515fab1b80d9e06a.tar.zst
no direct redb access
Diffstat (limited to 'base/src')
-rw-r--r--base/src/database.rs318
-rw-r--r--base/src/permission.rs11
2 files changed, 263 insertions, 66 deletions
diff --git a/base/src/database.rs b/base/src/database.rs
index 0195209..0f18097 100644
--- a/base/src/database.rs
+++ b/base/src/database.rs
@@ -3,44 +3,40 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use anyhow::Context;
+use anyhow::{anyhow, bail, Context, Result};
use bincode::{Decode, Encode};
use jellycommon::{
user::{NodeUserData, User},
- Node,
+ Node, NodeID,
};
use log::info;
-use redb::{Database, TableDefinition};
-use serde::{Deserialize, Serialize};
+use redb::{ReadableTable, TableDefinition};
use std::{
- borrow::Borrow,
fs::create_dir_all,
- ops::Deref,
path::Path,
sync::{Arc, RwLock},
};
use tantivy::{
+ collector::{Count, TopDocs},
directory::MmapDirectory,
- schema::{Field, Schema, FAST, INDEXED, STORED, STRING, TEXT},
- DateOptions, Index, IndexReader, IndexWriter, ReloadPolicy,
+ query::QueryParser,
+ schema::{Field, Schema, Value, FAST, INDEXED, STORED, STRING, TEXT},
+ DateOptions, Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument,
};
-pub use redb;
-pub use tantivy;
-
-pub const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user");
-pub const T_USER_NODE: TableDefinition<(&str, &str), Ser<NodeUserData>> =
+const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user");
+const T_USER_NODE: TableDefinition<(&str, [u8; 32]), Ser<NodeUserData>> =
TableDefinition::new("user_node");
-pub const T_INVITE: TableDefinition<&str, Ser<()>> = TableDefinition::new("invite");
-pub const T_NODE: TableDefinition<&str, Ser<Node>> = TableDefinition::new("node");
+const T_INVITE: TableDefinition<&str, ()> = TableDefinition::new("invite");
+const T_NODE: TableDefinition<[u8; 32], Ser<Node>> = TableDefinition::new("node");
#[derive(Clone)]
-pub struct DataAcid {
- pub inner: Arc<redb::Database>,
- pub node_index: Arc<NodeIndex>,
+pub struct Database {
+ inner: Arc<redb::Database>,
+ node_index: Arc<NodeIndex>,
}
-impl DataAcid {
+impl Database {
pub fn open(path: &Path) -> Result<Self, anyhow::Error> {
create_dir_all(path).context("creating database directory")?;
info!("opening kv store...");
@@ -54,7 +50,7 @@ impl DataAcid {
{
// this creates all tables such that read operations on them do not fail.
- let txn = r.begin_write()?;
+ let txn = r.inner.begin_write()?;
drop(txn.open_table(T_INVITE)?);
drop(txn.open_table(T_USER)?);
drop(txn.open_table(T_USER_NODE)?);
@@ -65,12 +61,212 @@ impl DataAcid {
info!("ready");
Ok(r)
}
-}
+ pub fn get_node_slug(&self, slug: &str) -> Result<Option<Arc<Node>>> {
+ self.get_node(NodeID::from_slug(slug))
+ }
+ pub fn get_node(&self, id: NodeID) -> Result<Option<Arc<Node>>> {
+ let txn = self.inner.begin_read()?;
+ let t_node = txn.open_table(T_NODE)?;
+ if let Some(node) = t_node.get(id.0)? {
+ Ok(Some(node.value().0.into()))
+ } else {
+ Ok(None)
+ }
+ }
+ pub fn get_node_udata(&self, id: NodeID, username: &str) -> Result<Option<NodeUserData>> {
+ let txn = self.inner.begin_read()?;
+ let t_node = txn.open_table(T_USER_NODE)?;
+ if let Some(node) = t_node.get((username, id.0))? {
+ Ok(Some(node.value().0.into()))
+ } else {
+ Ok(None)
+ }
+ }
+ pub fn get_user(&self, username: &str) -> Result<Option<User>> {
+ let txn = self.inner.begin_read()?;
+ let t_user = txn.open_table(T_USER)?;
+ if let Some(user) = t_user.get(username)? {
+ Ok(Some(user.value().0))
+ } else {
+ Ok(None)
+ }
+ }
+ pub fn update_user(
+ &self,
+ username: &str,
+ update: impl FnOnce(&mut User) -> Result<()>,
+ ) -> Result<()> {
+ let txn = self.inner.begin_write()?;
+ let mut users = txn.open_table(T_USER)?;
+ let mut user = users
+ .get(username)?
+ .ok_or(anyhow!("user does not exist"))?
+ .value()
+ .0;
+ update(&mut user)?;
+ users.insert(&username, Ser(user))?;
+ drop(users);
+ txn.commit()?;
+
+ Ok(())
+ }
+ pub fn update_node_udata(
+ &self,
+ node: NodeID,
+ username: &str,
+ update: impl FnOnce(&mut NodeUserData) -> Result<()>,
+ ) -> Result<()> {
+ let txn = self.inner.begin_write()?;
+ let mut user_nodes = txn.open_table(T_USER_NODE)?;
+
+ let mut udata = user_nodes
+ .get((username, node.0))?
+ .map(|x| x.value().0)
+ .unwrap_or_default();
-impl Deref for DataAcid {
- type Target = Database;
- fn deref(&self) -> &Self::Target {
- &self.inner
+ update(&mut udata)?;
+
+ user_nodes.insert((username, node.0), Ser(udata))?;
+ drop(user_nodes);
+ txn.commit()?;
+ Ok(())
+ }
+ pub fn delete_user(&self, username: &str) -> Result<bool> {
+ let txn = self.inner.begin_write()?;
+ let mut table = txn.open_table(T_USER)?;
+ let r = table.remove(username)?.is_some();
+ drop(table);
+ txn.commit()?;
+ Ok(r)
+ }
+ pub fn list_users(&self) -> Result<Vec<User>> {
+ let txn = self.inner.begin_read()?;
+ let table = txn.open_table(T_USER)?;
+ let i = table
+ .iter()?
+ .map(|a| {
+ let (_, y) = a.unwrap(); // TODO
+ y.value().0
+ })
+ .collect::<Vec<_>>();
+ drop(table);
+ Ok(i)
+ }
+ pub fn list_invites(&self) -> Result<Vec<String>> {
+ let txn = self.inner.begin_read()?;
+ let table = txn.open_table(T_INVITE)?;
+ let i = table
+ .iter()?
+ .map(|a| {
+ let (x, _) = a.unwrap();
+ x.value().to_owned()
+ })
+ .collect::<Vec<_>>();
+ drop(table);
+ Ok(i)
+ }
+ pub fn create_invite(&self, inv: &str) -> Result<()> {
+ let txn = self.inner.begin_write()?;
+ let mut table = txn.open_table(T_INVITE)?;
+ table.insert(inv, ())?;
+ drop(table);
+ txn.commit()?;
+ Ok(())
+ }
+ pub fn delete_invite(&self, inv: &str) -> Result<bool> {
+ let txn = self.inner.begin_write()?;
+ let mut table = txn.open_table(T_INVITE)?;
+ let r = table.remove(inv)?.is_some();
+ drop(table);
+ txn.commit()?;
+ Ok(r)
+ }
+ pub fn register_user(&self, invite: &str, username: &str, user: User) -> Result<()> {
+ let txn = self.inner.begin_write()?;
+ let mut invites = txn.open_table(T_INVITE)?;
+ let mut users = txn.open_table(T_USER)?;
+
+ if invites.remove(invite)?.is_none() {
+ bail!("invitation invalid");
+ }
+ let prev_user = users.insert(username, Ser(user))?.map(|x| x.value().0);
+ if prev_user.is_some() {
+ bail!("username taken");
+ }
+
+ drop(users);
+ drop(invites);
+ txn.commit()?;
+ Ok(())
+ }
+ pub fn list_nodes_with_udata(&self, username: &str) -> Result<Vec<(Node, NodeUserData)>> {
+ let txn = self.inner.begin_read()?;
+ let nodes = txn.open_table(T_NODE)?;
+ let node_users = txn.open_table(T_USER_NODE)?;
+ let i = nodes
+ .iter()?
+ .map(|a| {
+ let (x, y) = a.unwrap();
+ let (x, y) = (x.value().to_owned(), y.value().0);
+ let z = node_users
+ .get(&(username, x))
+ .unwrap()
+ .map(|z| z.value().0)
+ .unwrap_or_default();
+ (y, z)
+ })
+ .collect::<Vec<_>>();
+ drop(nodes);
+ Ok(i)
+ }
+ pub fn search(&self, query: &str, page: usize) -> Result<(usize, Vec<NodeID>)> {
+ let query = QueryParser::for_index(
+ &self.node_index.index,
+ vec![self.node_index.title, self.node_index.description],
+ )
+ .parse_query(query)
+ .context("parsing query")?;
+
+ let searcher = self.node_index.reader.searcher();
+ let sres = searcher.search(&query, &TopDocs::with_limit(32).and_offset(page * 32))?;
+ let scount = searcher.search(&query, &Count)?;
+
+ let mut results = Vec::new();
+ for (_, daddr) in sres {
+ let doc: TantivyDocument = searcher.doc(daddr)?;
+ let id = doc
+ .get_first(self.node_index.id)
+ .unwrap()
+ .as_bytes()
+ .unwrap();
+ let id = NodeID(id.try_into().unwrap());
+ results.push(id);
+ }
+ Ok((scount, results))
+ }
+ pub fn create_admin_user(&self, username: &str, password_hash: Vec<u8>) -> Result<()> {
+ let txn = self.inner.begin_write().unwrap();
+ let mut users = txn.open_table(T_USER).unwrap();
+
+ let admin = users.get(username).unwrap().map(|x| x.value().0);
+ users
+ .insert(
+ username,
+ Ser(User {
+ admin: true,
+ name: username.to_owned(),
+ password: password_hash,
+ ..admin.unwrap_or_else(|| User {
+ display_name: "Admin".to_string(),
+ ..Default::default()
+ })
+ }),
+ )
+ .unwrap();
+
+ drop(users);
+ txn.commit().unwrap();
+ Ok(())
}
}
@@ -126,42 +322,42 @@ impl NodeIndex {
}
}
-pub trait TableExt<Key, KeyRef, Value> {
- fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>;
- fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()>;
- fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>;
-}
-impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt<Key, KeyRef, Value>
- for redb::TableDefinition<'a, Key, Ser<Value>>
-where
- Key: Borrow<<Key as redb::Value>::SelfType<'b>> + redb::Key,
- Value: Encode + Decode + std::fmt::Debug + Serialize + for<'x> Deserialize<'x>,
- KeyRef: Borrow<<Key as redb::Value>::SelfType<'c>>,
-{
- fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> {
- let txn = db.inner.begin_read()?;
- let table = txn.open_table(self)?;
- let user = table.get(key)?.map(|v| v.value().0);
- drop(table);
- Ok(user)
- }
- fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()> {
- let txn = db.inner.begin_write()?;
- let mut table = txn.open_table(self)?;
- table.insert(key, Ser(value))?;
- drop(table);
- txn.commit()?;
- Ok(())
- }
- fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> {
- let txn = db.inner.begin_write()?;
- let mut table = txn.open_table(self)?;
- let prev = table.remove(key)?.map(|v| v.value().0);
- drop(table);
- txn.commit()?;
- Ok(prev)
- }
-}
+// pub trait TableExt<Key, KeyRef, Value> {
+// fn get(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>>;
+// fn insert(self, db: &Database, key: KeyRef, value: Value) -> anyhow::Result<()>;
+// fn remove(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>>;
+// }
+// impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt<Key, KeyRef, Value>
+// for redb::TableDefinition<'a, Key, Ser<Value>>
+// where
+// Key: Borrow<<Key as redb::Value>::SelfType<'b>> + redb::Key,
+// Value: Encode + Decode + std::fmt::Debug + Serialize + for<'x> Deserialize<'x>,
+// KeyRef: Borrow<<Key as redb::Value>::SelfType<'c>>,
+// {
+// fn get(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>> {
+// let txn = db.inner.begin_read()?;
+// let table = txn.open_table(self)?;
+// let user = table.get(key)?.map(|v| v.value().0);
+// drop(table);
+// Ok(user)
+// }
+// fn insert(self, db: &Database, key: KeyRef, value: Value) -> anyhow::Result<()> {
+// let txn = db.inner.begin_write()?;
+// let mut table = txn.open_table(self)?;
+// table.insert(key, Ser(value))?;
+// drop(table);
+// txn.commit()?;
+// Ok(())
+// }
+// fn remove(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>> {
+// let txn = db.inner.begin_write()?;
+// let mut table = txn.open_table(self)?;
+// let prev = table.remove(key)?.map(|v| v.value().0);
+// drop(table);
+// txn.commit()?;
+// Ok(prev)
+// }
+// }
// pub trait TableIterExt<
// 'a,
diff --git a/base/src/permission.rs b/base/src/permission.rs
index 9dc8ffe..cec3833 100644
--- a/base/src/permission.rs
+++ b/base/src/permission.rs
@@ -54,11 +54,12 @@ fn check_node_permission(perms: &PermissionSet, node: &Node) -> bool {
if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(node.id.clone().unwrap())) {
v
} else {
- for com in node.parents.clone().into_iter() {
- if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(com.to_owned())) {
- return v;
- }
- }
+ // TODO
+ // for com in node.parents.clone().into_iter() {
+ // if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(com.to_owned())) {
+ // return v;
+ // }
+ // }
true
}
}