diff --git a/.github/workflows/agdb_server.yaml b/.github/workflows/agdb_server.yaml index 34212a6e..20b386f7 100644 --- a/.github/workflows/agdb_server.yaml +++ b/.github/workflows/agdb_server.yaml @@ -36,7 +36,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: taiki-e/install-action@cargo-llvm-cov - run: rustup component add llvm-tools-preview - - run: cargo llvm-cov --package agdb_server --all-features --ignore-filename-regex "agdb(.|..)src|agdb_derive|agdb_api|api.rs" --fail-uncovered-functions 18 --show-missing-lines + - run: cargo llvm-cov --package agdb_server --all-features --ignore-filename-regex "agdb(.|..)src|agdb_derive|agdb_api|api.rs" --fail-uncovered-functions 21 --show-missing-lines agdb_server_test: runs-on: ubuntu-latest diff --git a/agdb_server/src/action/db_add.rs b/agdb_server/src/action/db_add.rs index 777ec957..7b589000 100644 --- a/agdb_server/src/action/db_add.rs +++ b/agdb_server/src/action/db_add.rs @@ -4,7 +4,6 @@ use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_db::Database; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbType; use serde::Deserialize; @@ -19,19 +18,15 @@ pub(crate) struct DbAdd { impl Action for DbAdd { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); - - let backup = db_pool - .add_db(&self.owner, &self.db, &name, self.db_type) - .await?; - + let backup = db_pool.add_db(&self.owner, &self.db, self.db_type).await?; let owner = db.user_id(&self.owner).await?; db.insert_db( owner, Database { db_id: None, - name, + db: self.db, + owner: self.owner, db_type: self.db_type, backup, }, diff --git a/agdb_server/src/action/db_backup.rs b/agdb_server/src/action/db_backup.rs index 1618187f..5b8d369a 100644 --- a/agdb_server/src/action/db_backup.rs +++ b/agdb_server/src/action/db_backup.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -16,11 +15,10 @@ pub(crate) struct DbBackup { impl Action for DbBackup { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let owner = db.user_id(&self.owner).await?; - let name = db_name(&self.owner, &self.db); - let mut database = db.user_db(owner, &name).await?; + let user = db.user_id(&self.owner).await?; + let mut database = db.user_db(user, &self.owner, &self.db).await?; database.backup = db_pool - .backup_db(&self.owner, &self.db, &name, database.db_type) + .backup_db(&self.owner, &self.db, database.db_type) .await?; db.save_db(&database).await?; Ok(ClusterActionResult::None) diff --git a/agdb_server/src/action/db_clear.rs b/agdb_server/src/action/db_clear.rs index 96decc05..b6b9fd43 100644 --- a/agdb_server/src/action/db_clear.rs +++ b/agdb_server/src/action/db_clear.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbResource; use serde::Deserialize; @@ -18,9 +17,8 @@ pub(crate) struct DbClear { impl Action for DbClear { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let owner = db.user_id(&self.owner).await?; - let name = db_name(&self.owner, &self.db); - let mut database = db.user_db(owner, &name).await?; + let user = db.user_id(&self.owner).await?; + let mut database = db.user_db(user, &self.owner, &self.db).await?; db_pool .clear_db(&self.owner, &self.db, &mut database, self.resource) .await?; diff --git a/agdb_server/src/action/db_convert.rs b/agdb_server/src/action/db_convert.rs index 165bf8e9..15721d12 100644 --- a/agdb_server/src/action/db_convert.rs +++ b/agdb_server/src/action/db_convert.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbType; use serde::Deserialize; @@ -18,11 +17,10 @@ pub(crate) struct DbConvert { impl Action for DbConvert { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let owner = db.user_id(&self.owner).await?; - let name = db_name(&self.owner, &self.db); - let mut database = db.user_db(owner, &name).await?; + let user = db.user_id(&self.owner).await?; + let mut database = db.user_db(user, &self.owner, &self.db).await?; db_pool - .convert_db(&self.owner, &self.db, &name, database.db_type, self.db_type) + .convert_db(&self.owner, &self.db, database.db_type, self.db_type) .await?; database.db_type = self.db_type; db.save_db(&database).await?; diff --git a/agdb_server/src/action/db_copy.rs b/agdb_server/src/action/db_copy.rs index d82d0998..c1b0fcdd 100644 --- a/agdb_server/src/action/db_copy.rs +++ b/agdb_server/src/action/db_copy.rs @@ -4,7 +4,6 @@ use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_db::Database; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbType; use serde::Deserialize; @@ -21,17 +20,16 @@ pub(crate) struct DbCopy { impl Action for DbCopy { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); - let target_name = db_name(&self.new_owner, &self.new_db); let new_owner_id = db.user_id(&self.new_owner).await?; db_pool - .copy_db(&name, &self.new_owner, &self.new_db, &target_name) + .copy_db(&self.owner, &self.db, &self.new_owner, &self.new_db) .await?; db.insert_db( new_owner_id, Database { db_id: None, - name: target_name, + db: self.new_db, + owner: self.new_owner, db_type: self.db_type, backup: 0, }, diff --git a/agdb_server/src/action/db_delete.rs b/agdb_server/src/action/db_delete.rs index 89884070..312eb2d9 100644 --- a/agdb_server/src/action/db_delete.rs +++ b/agdb_server/src/action/db_delete.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -16,10 +15,9 @@ pub(crate) struct DbDelete { impl Action for DbDelete { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); let user_id = db.user_id(&self.owner).await?; - db.remove_db(user_id, &name).await?; - db_pool.delete_db(&self.owner, &self.db, &name).await?; + db.remove_db(user_id, &self.owner, &self.db).await?; + db_pool.delete_db(&self.owner, &self.db).await?; Ok(ClusterActionResult::None) } } diff --git a/agdb_server/src/action/db_exec.rs b/agdb_server/src/action/db_exec.rs index 0cc93d56..b04a72a1 100644 --- a/agdb_server/src/action/db_exec.rs +++ b/agdb_server/src/action/db_exec.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::DbUserValue; use agdb::QueryResult; use agdb_api::Queries; @@ -20,10 +19,9 @@ pub(crate) struct DbExec { impl Action for DbExec { async fn exec(self, _db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); Ok(ClusterActionResult::QueryResults( db_pool - .exec_mut(&self.owner, &self.db, &name, &self.user, self.queries) + .exec_mut(&self.owner, &self.db, &self.user, self.queries) .await?, )) } diff --git a/agdb_server/src/action/db_optimize.rs b/agdb_server/src/action/db_optimize.rs index 13d6633b..8f6dfff5 100644 --- a/agdb_server/src/action/db_optimize.rs +++ b/agdb_server/src/action/db_optimize.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -16,8 +15,7 @@ pub(crate) struct DbOptimize { impl Action for DbOptimize { async fn exec(self, _db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); - db_pool.optimize_db(&name).await?; + db_pool.optimize_db(&self.owner, &self.db).await?; Ok(ClusterActionResult::None) } diff --git a/agdb_server/src/action/db_remove.rs b/agdb_server/src/action/db_remove.rs index 3f59bbe8..cbe98de7 100644 --- a/agdb_server/src/action/db_remove.rs +++ b/agdb_server/src/action/db_remove.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -16,10 +15,9 @@ pub(crate) struct DbRemove { impl Action for DbRemove { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); let user_id = db.user_id(&self.owner).await?; - db.remove_db(user_id, &name).await?; - db_pool.remove_db(&name).await?; + db.remove_db(user_id, &self.owner, &self.db).await?; + db_pool.remove_db(&self.owner, &self.db).await?; Ok(ClusterActionResult::None) } } diff --git a/agdb_server/src/action/db_rename.rs b/agdb_server/src/action/db_rename.rs index a7d8ee9c..05fceceb 100644 --- a/agdb_server/src/action/db_rename.rs +++ b/agdb_server/src/action/db_rename.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbUserRole; use serde::Deserialize; @@ -20,20 +19,12 @@ pub(crate) struct DbRename { impl Action for DbRename { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { let owner_id = db.user_id(&self.owner).await?; - let name = db_name(&self.owner, &self.db); - let new_name = db_name(&self.new_owner, &self.new_db); - let mut database = db.user_db(owner_id, &name).await?; + let mut database = db.user_db(owner_id, &self.owner, &self.db).await?; db_pool - .rename_db( - &self.owner, - &self.db, - &name, - &self.new_owner, - &self.new_db, - &new_name, - ) + .rename_db(&self.owner, &self.db, &self.new_owner, &self.new_db) .await?; - database.name = new_name; + database.owner = self.new_owner.clone(); + database.db = self.new_db.to_string(); db.save_db(&database).await?; if self.owner != self.new_owner { diff --git a/agdb_server/src/action/db_restore.rs b/agdb_server/src/action/db_restore.rs index b8f17021..476e36f7 100644 --- a/agdb_server/src/action/db_restore.rs +++ b/agdb_server/src/action/db_restore.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -16,12 +15,11 @@ pub(crate) struct DbRestore { impl Action for DbRestore { async fn exec(self, db: ServerDb, db_pool: DbPool) -> ServerResult { - let owner = db.user_id(&self.owner).await?; - let name = db_name(&self.owner, &self.db); - let mut database = db.user_db(owner, &name).await?; + let user = db.user_id(&self.owner).await?; + let mut database = db.user_db(user, &self.owner, &self.db).await?; if let Some(backup) = db_pool - .restore_db(&self.owner, &self.db, &name, database.db_type) + .restore_db(&self.owner, &self.db, database.db_type) .await? { database.backup = backup; diff --git a/agdb_server/src/action/db_user_add.rs b/agdb_server/src/action/db_user_add.rs index aa185b66..43bfb8cc 100644 --- a/agdb_server/src/action/db_user_add.rs +++ b/agdb_server/src/action/db_user_add.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use agdb_api::DbUserRole; use serde::Deserialize; @@ -19,9 +18,8 @@ pub(crate) struct DbUserAdd { impl Action for DbUserAdd { async fn exec(self, db: ServerDb, _db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); let owner_id = db.user_id(&self.owner).await?; - let db_id = db.user_db_id(owner_id, &name).await?; + let db_id = db.user_db_id(owner_id, &self.owner, &self.db).await?; let user_id = db.user_id(&self.user).await?; db.insert_db_user(db_id, user_id, self.db_role).await?; diff --git a/agdb_server/src/action/db_user_remove.rs b/agdb_server/src/action/db_user_remove.rs index b0159b70..45411f52 100644 --- a/agdb_server/src/action/db_user_remove.rs +++ b/agdb_server/src/action/db_user_remove.rs @@ -3,7 +3,6 @@ use super::ServerDb; use crate::action::Action; use crate::action::ClusterActionResult; use crate::server_error::ServerResult; -use crate::utilities::db_name; use agdb::UserValue; use serde::Deserialize; use serde::Serialize; @@ -17,9 +16,8 @@ pub(crate) struct DbUserRemove { impl Action for DbUserRemove { async fn exec(self, db: ServerDb, _db_pool: DbPool) -> ServerResult { - let name = db_name(&self.owner, &self.db); let owner_id = db.user_id(&self.owner).await?; - let db_id = db.user_db_id(owner_id, &name).await?; + let db_id = db.user_db_id(owner_id, &self.owner, &self.db).await?; let user_id = db.user_id(&self.user).await?; db.remove_db_user(db_id, user_id).await?; diff --git a/agdb_server/src/db_pool.rs b/agdb_server/src/db_pool.rs index 7bac4094..61d5f479 100644 --- a/agdb_server/src/db_pool.rs +++ b/agdb_server/src/db_pool.rs @@ -26,9 +26,24 @@ use std::time::UNIX_EPOCH; use tokio::sync::RwLock; use user_db::UserDb; +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct DbName { + pub(crate) owner: String, + pub(crate) db: String, +} + +impl DbName { + fn new(owner: &str, db: &str) -> Self { + Self { + owner: owner.to_string(), + db: db.to_string(), + } + } +} + #[derive(Clone)] pub(crate) struct DbPool { - pool: Arc>>, + pool: Arc>>, config: Config, } @@ -37,39 +52,32 @@ pub(crate) async fn new(config: Config, server_db: &ServerDb) -> ServerResult ServerResult { + async fn db(&self, owner: &str, db: &str) -> ServerResult { self.pool .read() .await - .get(name) + .get(&DbName::new(owner, db)) .cloned() .ok_or_else(|| ServerError::new(StatusCode::NOT_FOUND, "db not found")) } - pub(crate) async fn add_db( - &self, - owner: &str, - db: &str, - db_name: &str, - db_type: DbType, - ) -> ServerResult { - let db_path = Path::new(&self.config.data_dir).join(db_name); + pub(crate) async fn add_db(&self, owner: &str, db: &str, db_type: DbType) -> ServerResult { + let db_path = Path::new(&self.config.data_dir).join(owner).join(db); let path = db_path.to_str().ok_or(ErrorCode::DbInvalid)?.to_string(); std::fs::create_dir_all(db_audit_dir(owner, &self.config))?; - let user_db = UserDb::new(&format!("{}:{}", db_type, path)).map_err(|mut e| { + let user_db = UserDb::new(&format!("{db_type}:{path}")).map_err(|mut e| { e.status = ErrorCode::DbInvalid.into(); e.description = format!("{}: {}", ErrorCode::DbInvalid.as_str(), e.description); e @@ -81,7 +89,10 @@ impl DbPool { 0 }; - self.pool.write().await.insert(db_name.to_string(), user_db); + self.pool + .write() + .await + .insert(DbName::new(owner, db), user_db); Ok(backup) } @@ -102,10 +113,9 @@ impl DbPool { &self, owner: &str, db: &str, - db_name: &str, db_type: DbType, ) -> ServerResult { - let user_db = self.db(db_name).await?; + let user_db = self.db(owner, db).await?; let backup_path = if db_type == DbType::Memory { db_file(owner, db, &self.config) @@ -150,15 +160,8 @@ impl DbPool { Ok(()) } - pub(crate) async fn db_size(&self, name: &str) -> ServerResult { - Ok(self - .pool - .read() - .await - .get(name) - .ok_or(db_not_found(name))? - .size() - .await) + pub(crate) async fn db_size(&self, owner: &str, db: &str) -> ServerResult { + Ok(self.db(owner, db).await?.size().await) } async fn do_clear_db_backup( @@ -184,16 +187,19 @@ impl DbPool { database: &Database, ) -> Result<(), ServerError> { let mut pool = self.pool.write().await; - let user_db = pool - .get_mut(&database.name) - .ok_or(db_not_found(&database.name))?; - *user_db = UserDb::new(&format!("{}:{}", DbType::Memory, &database.name))?; + let name = database.name(); + let db_path = Path::new(&self.config.data_dir) + .join(owner) + .join(db) + .to_str() + .ok_or(ErrorCode::DbInvalid)? + .to_string(); + let user_db = pool.get_mut(&name).ok_or(db_not_found(owner, db))?; + *user_db = UserDb::new(&format!("{}:{db_path}", DbType::Memory))?; if database.db_type != DbType::Memory { remove_file_if_exists(db_file(owner, db, &self.config))?; remove_file_if_exists(db_file(owner, &format!(".{db}"), &self.config))?; - let db_path = Path::new(&self.config.data_dir).join(&database.name); - let path = db_path.to_str().ok_or(ErrorCode::DbInvalid)?.to_string(); - *user_db = UserDb::new(&format!("{}:{path}", database.db_type))?; + *user_db = UserDb::new(&format!("{}:{db_path}", database.db_type))?; } Ok(()) @@ -203,11 +209,11 @@ impl DbPool { &self, owner: &str, db: &str, - db_name: &str, db_type: DbType, target_type: DbType, ) -> ServerResult { - let mut user_db = self.pool.write().await.remove(db_name).unwrap(); + let db_name = DbName::new(owner, db); + let mut user_db = self.pool.write().await.remove(&db_name).unwrap(); let current_path = db_file(owner, db, &self.config); if db_type == DbType::Memory { @@ -224,17 +230,17 @@ impl DbPool { current_path.to_string_lossy() ))?; - self.pool.write().await.insert(db_name.to_string(), user_db); + self.pool.write().await.insert(db_name, user_db); Ok(()) } pub(crate) async fn copy_db( &self, - source_db: &str, + owner: &str, + db: &str, new_owner: &str, new_db: &str, - target_db: &str, ) -> ServerResult { let target_file = db_file(new_owner, new_db, &self.config); @@ -247,20 +253,18 @@ impl DbPool { std::fs::create_dir_all(Path::new(&self.config.data_dir).join(new_owner))?; let user_db = self - .db(source_db) + .db(owner, db) .await? .copy(target_file.to_string_lossy().as_ref()) .await?; - self.pool - .write() - .await - .insert(target_db.to_string(), user_db); + let target_db = DbName::new(new_owner, new_db); + self.pool.write().await.insert(target_db, user_db); Ok(()) } - pub(crate) async fn delete_db(&self, owner: &str, db: &str, db_name: &str) -> ServerResult { - self.remove_db(db_name).await?; + pub(crate) async fn delete_db(&self, owner: &str, db: &str) -> ServerResult { + self.remove_db(owner, db).await?; remove_file_if_exists(db_file(owner, db, &self.config))?; remove_file_if_exists(db_file(owner, &format!(".{db}"), &self.config))?; remove_file_if_exists(db_backup_file(owner, db, &self.config))?; @@ -269,21 +273,25 @@ impl DbPool { pub(crate) async fn exec( &self, - db_name: &str, + owner: &str, + db: &str, queries: Queries, ) -> ServerResult> { - self.db(db_name).await?.exec(queries).await + self.db(owner, db).await?.exec(queries).await } pub(crate) async fn exec_mut( &self, owner: &str, db: &str, - db_name: &str, username: &str, queries: Queries, ) -> ServerResult> { - let (r, audit) = self.db(db_name).await?.exec_mut(queries, username).await?; + let (r, audit) = self + .db(owner, db) + .await? + .exec_mut(queries, username) + .await?; if !audit.is_empty() { let mut log = std::fs::OpenOptions::new() @@ -305,17 +313,22 @@ impl DbPool { Ok(r) } - pub(crate) async fn optimize_db(&self, db_name: &str) -> ServerResult { - let user_db = self.db(db_name).await?; + pub(crate) async fn optimize_db(&self, owner: &str, db: &str) -> ServerResult { + let user_db = self.db(owner, db).await?; user_db.optimize_storage().await?; Ok(()) } - pub(crate) async fn remove_db(&self, db_name: &str) -> ServerResult { - Ok(self.pool.write().await.remove(db_name).unwrap()) + pub(crate) async fn remove_db(&self, owner: &str, db: &str) -> ServerResult { + Ok(self + .pool + .write() + .await + .remove(&DbName::new(owner, db)) + .unwrap()) } - pub(crate) async fn remove_user_dbs(&self, username: &str, dbs: &[String]) -> ServerResult { + pub(crate) async fn remove_user_dbs(&self, username: &str, dbs: &[DbName]) -> ServerResult { for db in dbs { self.pool.write().await.remove(db); } @@ -332,10 +345,8 @@ impl DbPool { &self, owner: &str, db: &str, - source_db: &str, new_owner: &str, new_db: &str, - target_db: &str, ) -> ServerResult { let target_name = db_file(new_owner, new_db, &self.config); @@ -347,7 +358,7 @@ impl DbPool { std::fs::create_dir_all(Path::new(&self.config.data_dir).join(new_owner))?; } - let user_db = self.db(source_db).await?; + let user_db = self.db(owner, db).await?; user_db .rename(target_name.to_string_lossy().as_ref()) @@ -366,11 +377,10 @@ impl DbPool { std::fs::rename(backup_path, new_backup_path)?; } - self.pool - .write() - .await - .insert(target_db.to_string(), user_db); - self.pool.write().await.remove(source_db).unwrap(); + let source_db = DbName::new(owner, db); + let target_db = DbName::new(new_owner, new_db); + self.pool.write().await.insert(target_db, user_db); + self.pool.write().await.remove(&source_db).unwrap(); Ok(()) } @@ -379,7 +389,6 @@ impl DbPool { &self, owner: &str, db: &str, - db_name: &str, db_type: DbType, ) -> ServerResult> { let backup_path = if db_type == DbType::Memory { @@ -395,7 +404,8 @@ impl DbPool { }); } - self.pool.write().await.remove(db_name); + let db_name = DbName::new(owner, db); + self.pool.write().await.remove(&db_name); let current_path = db_file(owner, db, &self.config); let backup = if db_type != DbType::Memory { @@ -409,14 +419,17 @@ impl DbPool { }; let user_db = UserDb::new(&format!("{}:{}", db_type, current_path.to_string_lossy()))?; - self.pool.write().await.insert(db_name.to_string(), user_db); + self.pool.write().await.insert(db_name, user_db); Ok(backup) } } -fn db_not_found(name: &str) -> ServerError { - ServerError::new(StatusCode::NOT_FOUND, &format!("db not found: {name}")) +fn db_not_found(owner: &str, db: &str) -> ServerError { + ServerError::new( + StatusCode::NOT_FOUND, + &format!("db not found: {owner}/{db}"), + ) } fn db_backup_file(owner: &str, db: &str, config: &Config) -> PathBuf { diff --git a/agdb_server/src/routes/admin/db.rs b/agdb_server/src/routes/admin/db.rs index ace0d5f6..cbe6640b 100644 --- a/agdb_server/src/routes/admin/db.rs +++ b/agdb_server/src/routes/admin/db.rs @@ -61,10 +61,13 @@ pub(crate) async fn add( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - if server_db.find_user_db_id(owner_id, &name).await?.is_some() { + if server_db + .find_user_db_id(owner_id, &owner, &db) + .await? + .is_some() + { return Err(ErrorCode::DbExists.into()); } @@ -102,9 +105,8 @@ pub(crate) async fn audit( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse<(StatusCode, Json)> { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - server_db.user_db_id(owner_id, &db_name).await?; + server_db.user_db_id(owner_id, &owner, &db).await?; Ok((StatusCode::OK, Json(db_pool.audit(&owner, &db).await?))) } @@ -131,9 +133,8 @@ pub(crate) async fn backup( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - server_db.user_db_id(owner_id, &db_name).await?; + server_db.user_db_id(owner_id, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbBackup { owner, db }).await?; @@ -167,9 +168,8 @@ pub(crate) async fn clear( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let role = server_db.user_db_role(owner_id, &db_name).await?; + let role = server_db.user_db_role(owner_id, &owner, &db).await?; let (commit_index, _result) = cluster .exec(DbClear { @@ -179,8 +179,8 @@ pub(crate) async fn clear( }) .await?; - let size = db_pool.db_size(&db_name).await.unwrap_or(0); - let database = server_db.user_db(owner_id, &db_name).await?; + let size = db_pool.db_size(&owner, &db).await.unwrap_or(0); + let database = server_db.user_db(owner_id, &owner, &db).await?; let db = ServerDatabase { db, owner, @@ -221,9 +221,8 @@ pub(crate) async fn convert( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let db_type = server_db.user_db(owner_id, &db_name).await?.db_type; + let db_type = server_db.user_db(owner_id, &owner, &db).await?.db_type; if db_type == request.db_type { return Ok((StatusCode::CREATED, [("commit-index", String::new())])); @@ -272,14 +271,12 @@ pub(crate) async fn copy( .new_name .split_once('/') .ok_or(ErrorCode::DbInvalid)?; - let source_db = db_name(&owner, &db); - let target_db = db_name(new_owner, new_db); let owner_id = server_db.user_id(&owner).await?; - let db_type = server_db.user_db(owner_id, &source_db).await?.db_type; + let db_type = server_db.user_db(owner_id, &owner, &db).await?.db_type; let new_owner_id = server_db.user_id(new_owner).await?; if server_db - .find_user_db_id(new_owner_id, &target_db) + .find_user_db_id(new_owner_id, new_owner, new_db) .await? .is_some() { @@ -324,7 +321,7 @@ pub(crate) async fn delete( Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { let user_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(user_id, &db_name(&owner, &db)).await?; + let _ = server_db.user_db_id(user_id, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbDelete { owner, db }).await?; @@ -357,14 +354,13 @@ pub(crate) async fn exec( Path((owner, db)): Path<(String, String)>, Json(queries): Json, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let required_role = required_role(&queries); if required_role != DbUserRole::Read { return Err(permission_denied( "mutable queries not allowed, use exec_mut endpoint", )); } - let results = db_pool.exec(&db_name, queries).await?; + let results = db_pool.exec(&owner, &db, queries).await?; Ok((StatusCode::OK, Json(QueriesResults(results)))) } @@ -392,11 +388,10 @@ pub(crate) async fn exec_mut( Path((owner, db)): Path<(String, String)>, Json(queries): Json, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let required_role = required_role(&queries); let (commit_index, results) = if required_role == DbUserRole::Read { - (0, db_pool.exec(&db_name, queries).await?) + (0, db_pool.exec(&owner, &db, queries).await?) } else { let mut index = 0; let mut results = Vec::new(); @@ -443,16 +438,14 @@ pub(crate) async fn list( let mut dbs = Vec::with_capacity(databases.len()); for db in databases { - if let Some((owner, name)) = db.name.split_once('/') { - dbs.push(ServerDatabase { - size: db_pool.db_size(&db.name).await.unwrap_or(0), - db: name.to_string(), - owner: owner.to_string(), - db_type: db.db_type, - role: DbUserRole::Admin, - backup: db.backup, - }); - } + dbs.push(ServerDatabase { + size: db_pool.db_size(&db.owner, &db.db).await.unwrap_or(0), + db: db.db, + owner: db.owner, + db_type: db.db_type, + role: DbUserRole::Admin, + backup: db.backup, + }); } Ok((StatusCode::OK, Json(dbs))) @@ -479,10 +472,9 @@ pub(crate) async fn optimize( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let database = server_db.user_db(owner_id, &db_name).await?; - let role = server_db.user_db_role(owner_id, &db_name).await?; + let database = server_db.user_db(owner_id, &owner, &db).await?; + let role = server_db.user_db_role(owner_id, &owner, &db).await?; let (commit_index, _result) = cluster .exec(DbOptimize { @@ -490,7 +482,7 @@ pub(crate) async fn optimize( db: db.clone(), }) .await?; - let size = db_pool.db_size(&db_name).await?; + let size = db_pool.db_size(&owner, &db).await?; Ok(( StatusCode::OK, @@ -528,7 +520,7 @@ pub(crate) async fn remove( Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { let user_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(user_id, &db_name(&owner, &db)).await?; + let _ = server_db.user_db_id(user_id, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbRemove { owner, db }).await?; @@ -563,11 +555,10 @@ pub(crate) async fn rename( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(owner_id, &db_name).await?; + let _ = server_db.user_db_id(owner_id, &owner, &db).await?; - if db_name == request.new_name { + if db_name(&owner, &db) == request.new_name { return Ok((StatusCode::CREATED, [("commit-index", String::new())])); } @@ -578,7 +569,7 @@ pub(crate) async fn rename( let new_owner_id = server_db.user_id(new_owner).await?; if server_db - .find_user_db_id(new_owner_id, &request.new_name) + .find_user_db_id(new_owner_id, new_owner, new_db) .await? .is_some() { @@ -621,9 +612,8 @@ pub(crate) async fn restore( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(owner_id, &db_name).await?; + let _ = server_db.user_db_id(owner_id, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbRestore { owner, db }).await?; diff --git a/agdb_server/src/routes/admin/db/user.rs b/agdb_server/src/routes/admin/db/user.rs index f6da1282..e70dc60d 100644 --- a/agdb_server/src/routes/admin/db/user.rs +++ b/agdb_server/src/routes/admin/db/user.rs @@ -6,7 +6,6 @@ use crate::server_db::ServerDb; use crate::server_error::permission_denied; use crate::server_error::ServerResponse; use crate::user_id::AdminId; -use crate::utilities::db_name; use agdb_api::DbUser; use axum::extract::Path; use axum::extract::Query; @@ -44,9 +43,8 @@ pub(crate) async fn add( return Err(permission_denied("cannot change role of db owner")); } - let name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(owner_id, &name).await?; + let _ = server_db.user_db_id(owner_id, &owner, &db).await?; let _ = server_db.user_id(&username).await?; let (commit_index, _result) = cluster @@ -85,9 +83,7 @@ pub(crate) async fn list( Path((owner, db)): Path<(String, String)>, ) -> ServerResponse<(StatusCode, Json>)> { let owner_id = server_db.user_id(&owner).await?; - let db_id = server_db - .user_db_id(owner_id, &db_name(&owner, &db)) - .await?; + let db_id = server_db.user_db_id(owner_id, &owner, &db).await?; Ok((StatusCode::OK, Json(server_db.db_users(db_id).await?))) } @@ -119,9 +115,8 @@ pub(crate) async fn remove( return Err(permission_denied("cannot remove owner")); } - let name = db_name(&owner, &db); let owner_id = server_db.user_id(&owner).await?; - let _ = server_db.user_db_id(owner_id, &name).await?; + let _ = server_db.user_db_id(owner_id, &owner, &db).await?; let _ = server_db.user_id(&username).await?; let (commit_index, _result) = cluster diff --git a/agdb_server/src/routes/db.rs b/agdb_server/src/routes/db.rs index 8a1d4719..ff37a619 100644 --- a/agdb_server/src/routes/db.rs +++ b/agdb_server/src/routes/db.rs @@ -91,15 +91,17 @@ pub(crate) async fn add( )); } - let name = db_name(&username, &db); - - if server_db.find_user_db_id(user.0, &name).await?.is_some() { + if server_db + .find_user_db_id(user.0, &owner, &db) + .await? + .is_some() + { return Err(ErrorCode::DbExists.into()); } let (commit_index, _result) = cluster .exec(DbAdd { - owner: username, + owner, db, db_type: request.db_type, }) @@ -132,8 +134,7 @@ pub(crate) async fn audit( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse<(StatusCode, Json)> { - let db_name = db_name(&owner, &db); - server_db.user_db_id(user.0, &db_name).await?; + server_db.user_db_id(user.0, &owner, &db).await?; Ok((StatusCode::OK, Json(db_pool.audit(&owner, &db).await?))) } @@ -160,8 +161,7 @@ pub(crate) async fn backup( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let db_id = server_db.user_db_id(user.0, &db_name).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; if !server_db.is_db_admin(user.0, db_id).await? { return Err(permission_denied("admin only")); @@ -200,9 +200,8 @@ pub(crate) async fn clear( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let db_id = server_db.user_db_id(user.0, &db_name).await?; - let role = server_db.user_db_role(user.0, &db_name).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; + let role = server_db.user_db_role(user.0, &owner, &db).await?; if !server_db.is_db_admin(user.0, db_id).await? { return Err(permission_denied("admin only")); @@ -216,8 +215,8 @@ pub(crate) async fn clear( }) .await?; - let size = db_pool.db_size(&db_name).await.unwrap_or(0); - let database = server_db.user_db(user.0, &db_name).await?; + let size = db_pool.db_size(&owner, &db).await.unwrap_or(0); + let database = server_db.user_db(user.0, &owner, &db).await?; let db = ServerDatabase { db, owner, @@ -258,11 +257,10 @@ pub(crate) async fn convert( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let database = server_db.user_db(user.0, &db_name).await?; + let database = server_db.user_db(user.0, &owner, &db).await?; if !server_db - .is_db_admin(user.0, database.db_id.unwrap()) + .is_db_admin(user.0, database.db_id.unwrap_or_default()) .await? { return Err(permission_denied("admin only")); @@ -316,9 +314,7 @@ pub(crate) async fn copy( .new_name .split_once('/') .ok_or(ErrorCode::DbInvalid)?; - let source_db = db_name(&owner, &db); - let target_db = db_name(new_owner, new_db); - let db_type = server_db.user_db(user.0, &source_db).await?.db_type; + let db_type = server_db.user_db(user.0, &owner, &db).await?.db_type; let username = server_db.user_name(user.0).await?; if new_owner != username { @@ -326,7 +322,7 @@ pub(crate) async fn copy( } if server_db - .find_user_db_id(user.0, &target_db) + .find_user_db_id(user.0, new_owner, new_db) .await? .is_some() { @@ -377,7 +373,7 @@ pub(crate) async fn delete( return Err(permission_denied("owner only")); } - let _ = server_db.user_db_id(user.0, &db_name(&owner, &db)).await?; + let _ = server_db.user_db_id(user.0, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbDelete { owner, db }).await?; @@ -405,19 +401,20 @@ pub(crate) async fn delete( ) )] pub(crate) async fn exec( - _user: UserId, + user: UserId, State(db_pool): State, + State(server_db): State, Path((owner, db)): Path<(String, String)>, Json(queries): Json, ) -> ServerResponse { - let db_name = db_name(&owner, &db); + let _ = server_db.user_db_id(user.0, &owner, &db).await?; let required_role = required_role(&queries); if required_role != DbUserRole::Read { return Err(permission_denied( "mutable queries not allowed, use exec_mut endpoint", )); } - let results = db_pool.exec(&db_name, queries).await?; + let results = db_pool.exec(&owner, &db, queries).await?; Ok((StatusCode::OK, Json(QueriesResults(results)))) } @@ -446,8 +443,7 @@ pub(crate) async fn exec_mut( Path((owner, db)): Path<(String, String)>, Json(queries): Json, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let role = server_db.user_db_role(user.0, &db_name).await?; + let role = server_db.user_db_role(user.0, &owner, &db).await?; let required_role = required_role(&queries); if role == DbUserRole::Read { @@ -455,7 +451,7 @@ pub(crate) async fn exec_mut( } let (commit_index, results) = if required_role == DbUserRole::Read { - (0, db_pool.exec(&db_name, queries).await?) + (0, db_pool.exec(&owner, &db, queries).await?) } else { let username = server_db.user_name(user.0).await?; let mut index = 0; @@ -503,7 +499,7 @@ pub(crate) async fn list( let mut sizes = Vec::with_capacity(databases.len()); for (_, db) in &databases { - sizes.push(db_pool.db_size(&db.name).await.unwrap_or(0)); + sizes.push(db_pool.db_size(&db.owner, &db.db).await.unwrap_or(0)); } let dbs = databases @@ -511,16 +507,14 @@ pub(crate) async fn list( .zip(sizes) .filter_map(|((role, db), size)| { if size != 0 { - if let Some((owner, name)) = db.name.split_once('/') { - return Some(ServerDatabase { - db: name.to_string(), - owner: owner.to_string(), - db_type: db.db_type, - role, - backup: db.backup, - size, - }); - } + return Some(ServerDatabase { + db: db.db, + owner: db.owner, + db_type: db.db_type, + role, + backup: db.backup, + size, + }); } None }) @@ -552,9 +546,8 @@ pub(crate) async fn optimize( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let database = server_db.user_db(user.0, &db_name).await?; - let role = server_db.user_db_role(user.0, &db_name).await?; + let database = server_db.user_db(user.0, &owner, &db).await?; + let role = server_db.user_db_role(user.0, &owner, &db).await?; if role == DbUserRole::Read { return Err(permission_denied("write rights required")); @@ -566,7 +559,7 @@ pub(crate) async fn optimize( db: db.clone(), }) .await?; - let size = db_pool.db_size(&db_name).await?; + let size = db_pool.db_size(&owner, &db).await?; Ok(( StatusCode::OK, @@ -610,7 +603,7 @@ pub(crate) async fn remove( return Err(permission_denied("owner only")); } - let _ = server_db.user_db_id(user.0, &db_name(&owner, &db)).await?; + let _ = server_db.user_db_id(user.0, &owner, &db).await?; let (commit_index, _result) = cluster.exec(DbRemove { owner, db }).await?; @@ -646,10 +639,9 @@ pub(crate) async fn rename( Path((owner, db)): Path<(String, String)>, request: Query, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let _ = server_db.user_db_id(user.0, &db_name).await?; + let _ = server_db.user_db_id(user.0, &owner, &db).await?; - if db_name == request.new_name { + if db_name(&owner, &db) == request.new_name { return Ok((StatusCode::CREATED, [("commit-index", String::new())])); } @@ -664,7 +656,7 @@ pub(crate) async fn rename( let new_owner_id = server_db.user_id(new_owner).await?; if server_db - .find_user_db_id(new_owner_id, &request.new_name) + .find_user_db_id(new_owner_id, new_owner, new_db) .await? .is_some() { @@ -708,8 +700,7 @@ pub(crate) async fn restore( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse { - let db_name = db_name(&owner, &db); - let db_id = server_db.user_db_id(user.0, &db_name).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; if !server_db.is_db_admin(user.0, db_id).await? { return Err(permission_denied("admin only")); diff --git a/agdb_server/src/routes/db/user.rs b/agdb_server/src/routes/db/user.rs index 465960e4..ed0e9487 100644 --- a/agdb_server/src/routes/db/user.rs +++ b/agdb_server/src/routes/db/user.rs @@ -5,7 +5,6 @@ use crate::server_db::ServerDb; use crate::server_error::permission_denied; use crate::server_error::ServerResponse; use crate::user_id::UserId; -use crate::utilities::db_name; use agdb_api::DbUser; use agdb_api::DbUserRole; use axum::extract::Path; @@ -53,8 +52,7 @@ pub(crate) async fn add( return Err(permission_denied("cannot change role of db owner")); } - let db_name = db_name(&owner, &db); - let db_id = server_db.user_db_id(user.0, &db_name).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; if !server_db.is_db_admin(user.0, db_id).await? { return Err(permission_denied("admin only")); @@ -97,7 +95,7 @@ pub(crate) async fn list( State(server_db): State, Path((owner, db)): Path<(String, String)>, ) -> ServerResponse<(StatusCode, Json>)> { - let db_id = server_db.user_db_id(user.0, &db_name(&owner, &db)).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; Ok((StatusCode::OK, Json(server_db.db_users(db_id).await?))) } @@ -129,7 +127,7 @@ pub(crate) async fn remove( return Err(permission_denied("cannot remove owner")); } - let db_id = server_db.user_db_id(user.0, &db_name(&owner, &db)).await?; + let db_id = server_db.user_db_id(user.0, &owner, &db).await?; let user_id = server_db.user_id(&username).await?; if user.0 != user_id && !server_db.is_db_admin(user.0, db_id).await? { diff --git a/agdb_server/src/server_db.rs b/agdb_server/src/server_db.rs index 3a50cdf3..c7d6fa14 100644 --- a/agdb_server/src/server_db.rs +++ b/agdb_server/src/server_db.rs @@ -17,6 +17,7 @@ use crate::action::user_add::UserAdd; use crate::action::user_remove::UserRemove; use crate::action::ClusterAction; use crate::config::Config; +use crate::db_pool::DbName; use crate::password::Password; use crate::raft::Log; use crate::server_error::ServerError; @@ -54,20 +55,31 @@ pub(crate) struct ServerUser { #[derive(Default, UserValue)] pub(crate) struct Database { pub(crate) db_id: Option, - pub(crate) name: String, + pub(crate) db: String, + pub(crate) owner: String, pub(crate) db_type: DbType, pub(crate) backup: u64, } +impl Database { + pub(crate) fn name(&self) -> DbName { + DbName { + owner: self.owner.clone(), + db: self.db.clone(), + } + } +} + #[derive(Clone)] pub(crate) struct ServerDb(pub(crate) Arc>); const ADMIN: &str = "admin"; const CLUSTER_LOG: &str = "cluster_log"; const COMMITTED: &str = "committed"; +const DB: &str = "db"; const DBS: &str = "dbs"; const EXECUTED: &str = "executed"; -const NAME: &str = "name"; +const OWNER: &str = "owner"; const ROLE: &str = "role"; const TOKEN: &str = "token"; const USERS: &str = "users"; @@ -142,6 +154,40 @@ impl ServerDb { t.exec_mut(QueryBuilder::insert().nodes().aliases(CLUSTER_LOG).query())?; } + // Migration to new Database struct introduced in 0.10.0. Remove in 0.12.0. + let dbs: Vec<(DbId, String, String)> = t + .exec( + QueryBuilder::select() + .values("name") + .search() + .from(DBS) + .where_() + .distance(CountComparison::Equal(2)) + .and() + .keys("name") + .query(), + )? + .elements + .iter() + .filter_map(|e| { + e.values[0] + .value + .to_string() + .split_once('/') + .map(|(owner, db)| (e.id, owner.to_string(), db.to_string())) + }) + .collect::>(); + + for (db_id, owner, db) in dbs { + t.exec_mut( + QueryBuilder::insert() + .values([[(OWNER, owner).into(), (DB, db).into()]]) + .ids(db_id) + .query(), + )?; + t.exec_mut(QueryBuilder::remove().values("name").ids(db_id).query())?; + } + Ok(()) })?; @@ -280,12 +326,17 @@ impl ServerDb { .try_into()?) } - pub(crate) async fn find_user_db_id(&self, user: DbId, db: &str) -> ServerResult> { + pub(crate) async fn find_user_db_id( + &self, + user: DbId, + owner: &str, + db: &str, + ) -> ServerResult> { Ok(self .0 .read() .await - .exec(find_user_db_query(user, db))? + .exec(find_user_db_query(user, owner, db))? .elements .first() .map(|e| e.id)) @@ -531,10 +582,10 @@ impl ServerDb { }) } - pub(crate) async fn remove_db(&self, user: DbId, db: &str) -> ServerResult<()> { + pub(crate) async fn remove_db(&self, user: DbId, owner: &str, db: &str) -> ServerResult<()> { self.0.write().await.transaction_mut(|t| { let db_id = t - .exec(find_user_db_query(user, db))? + .exec(find_user_db_query(user, owner, db))? .elements .first() .ok_or(db_not_found(db))? @@ -560,7 +611,7 @@ impl ServerDb { Ok(()) } - pub(crate) async fn remove_user(&self, username: &str) -> ServerResult> { + pub(crate) async fn remove_user(&self, username: &str) -> ServerResult> { let user = self.user_id(username).await?; let mut ids = vec![user]; let mut dbs = vec![]; @@ -569,11 +620,12 @@ impl ServerDb { .await? .into_iter() .for_each(|(_role, db)| { - if let Some((owner, _)) = db.name.split_once('/') { - if owner == username { - ids.push(db.db_id.unwrap()); - dbs.push(db.name); - } + if db.owner == username { + ids.push(db.db_id.unwrap()); + dbs.push(DbName { + owner: db.owner, + db: db.db, + }); } }); @@ -651,34 +703,44 @@ impl ServerDb { .try_into()?) } - pub(crate) async fn user_db(&self, user: DbId, db: &str) -> ServerResult { + pub(crate) async fn user_db( + &self, + user: DbId, + owner: &str, + db: &str, + ) -> ServerResult { self.0 .read() .await .exec( QueryBuilder::select() .elements::() - .ids(find_user_db_query(user, db)) + .ids(find_user_db_query(user, owner, db)) .query(), )? .try_into() .map_err(|_| db_not_found(db)) } - pub(crate) async fn user_db_id(&self, user: DbId, db: &str) -> ServerResult { - self.find_user_db_id(user, db) + pub(crate) async fn user_db_id(&self, user: DbId, owner: &str, db: &str) -> ServerResult { + self.find_user_db_id(user, owner, db) .await? .ok_or(db_not_found(db)) } - pub(crate) async fn user_db_role(&self, user: DbId, db: &str) -> ServerResult { + pub(crate) async fn user_db_role( + &self, + user: DbId, + owner: &str, + db: &str, + ) -> ServerResult { Ok((&self .0 .read() .await .transaction(|t| -> Result { let db_id = t - .exec(find_user_db_query(user, db))? + .exec(find_user_db_query(user, owner, db))? .elements .first() .ok_or(db_not_found(db))? @@ -834,7 +896,7 @@ fn db_not_found(name: &str) -> ServerError { ServerError::new(StatusCode::NOT_FOUND, &format!("db not found: {name}")) } -fn find_user_db_query(user: DbId, db: &str) -> SearchQuery { +fn find_user_db_query(user: DbId, owner: &str, db: &str) -> SearchQuery { QueryBuilder::search() .depth_first() .from(user) @@ -842,7 +904,10 @@ fn find_user_db_query(user: DbId, db: &str) -> SearchQuery { .where_() .distance(CountComparison::Equal(2)) .and() - .key(NAME) + .key(OWNER) + .value(Comparison::Equal(owner.into())) + .and() + .key(DB) .value(Comparison::Equal(db.into())) .query() } @@ -1128,3 +1193,69 @@ fn logs( Ok(actions) } + +#[cfg(test)] +mod tests { + use super::*; + + struct TestFile { + filename: &'static str, + } + + impl TestFile { + fn new(filename: &'static str) -> Self { + let _ = std::fs::remove_file(filename); + Self { filename } + } + } + + impl Drop for TestFile { + fn drop(&mut self) { + let _ = std::fs::remove_file(self.filename); + } + } + + #[tokio::test] + async fn db_upgrade() -> ServerResult { + let file = TestFile::new("test_db.db"); + let _dot_file = TestFile::new(".test_db.db"); + let mut db = Db::new(file.filename)?; + db.transaction_mut(|t| -> ServerResult { + t.exec_mut(QueryBuilder::insert().nodes().aliases(DBS).query())?; + let user_db = t.exec_mut( + QueryBuilder::insert() + .nodes() + .values([ + vec![ + ("name", "user/db1").into(), + ("db_type", DbType::Memory).into(), + ("backup", 0).into(), + ], + vec![ + ("owner", "user").into(), + ("db", "db2").into(), + ("db_type", DbType::Memory).into(), + ("backup", 0).into(), + ], + ]) + .query(), + )?; + t.exec_mut(QueryBuilder::insert().edges().from(DBS).to(user_db).query())?; + Ok(()) + })?; + + let db = ServerDb::new("test_db.db")?; + let dbs = db.dbs().await?; + assert_eq!(dbs.len(), 2); + assert_eq!(dbs[0].db, "db2"); + assert_eq!(dbs[0].owner, "user"); + assert_eq!(dbs[0].db_type, DbType::Memory); + assert_eq!(dbs[0].backup, 0); + assert_eq!(dbs[1].db, "db1"); + assert_eq!(dbs[1].owner, "user"); + assert_eq!(dbs[1].db_type, DbType::Memory); + assert_eq!(dbs[1].backup, 0); + + Ok(()) + } +} diff --git a/agdb_server/tests/routes/db_exec_test.rs b/agdb_server/tests/routes/db_exec_test.rs index 917cec6e..bff28a48 100644 --- a/agdb_server/tests/routes/db_exec_test.rs +++ b/agdb_server/tests/routes/db_exec_test.rs @@ -744,6 +744,22 @@ async fn db_not_found() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn someone_elses_db() -> anyhow::Result<()> { + let mut server = TestServer::new().await?; + let owner = &next_user_name(); + let db = &next_db_name(); + let user = &next_user_name(); + server.api.user_login(ADMIN, ADMIN).await?; + server.api.admin_user_add(owner, owner).await?; + server.api.admin_user_add(user, user).await?; + server.api.admin_db_add(owner, db, DbType::Memory).await?; + server.api.user_login(user, user).await?; + let status = server.api.db_exec(owner, db, &[]).await.unwrap_err().status; + assert_eq!(status, 404); + Ok(()) +} + #[tokio::test] async fn no_token() -> anyhow::Result<()> { let server = TestServer::new().await?;