diff options
| -rw-r--r-- | server/.sqlx/query-7715484cf01394aad53517f6e68a116e9f1b67bc75eb459a45d18f9c67e03e34.json | 47 | ||||
| -rw-r--r-- | server/.sqlx/query-d951a84ad9d38b745f12217a31200667ca4d499d06b37e2ae9c36e65c0eb3c49.json | 12 | ||||
| -rw-r--r-- | server/.sqlx/query-e53285109ec191077d218ec990020165f44b8432d9c2583c9f14cd7d7c2e37bb.json | 47 | ||||
| -rw-r--r-- | server/common/src/git.rs | 13 | ||||
| -rw-r--r-- | server/common/src/tests.rs | 19 | ||||
| -rw-r--r-- | server/src/git_root.rs | 14 | ||||
| -rw-r--r-- | server/src/main.rs | 176 |
7 files changed, 327 insertions, 1 deletions
diff --git a/server/.sqlx/query-7715484cf01394aad53517f6e68a116e9f1b67bc75eb459a45d18f9c67e03e34.json b/server/.sqlx/query-7715484cf01394aad53517f6e68a116e9f1b67bc75eb459a45d18f9c67e03e34.json new file mode 100644 index 0000000..1cf179c --- /dev/null +++ b/server/.sqlx/query-7715484cf01394aad53517f6e68a116e9f1b67bc75eb459a45d18f9c67e03e34.json @@ -0,0 +1,47 @@ +{ + "db_name": "MySQL", + "query": "SELECT state,branch,owner FROM reviews WHERE project=? AND id=?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "state", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL | UNSIGNED", + "char_set": 63, + "max_size": 3 + } + }, + { + "ordinal": 1, + "name": "branch", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 4096 + } + }, + { + "ordinal": 2, + "name": "owner", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 512 + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "7715484cf01394aad53517f6e68a116e9f1b67bc75eb459a45d18f9c67e03e34" +} diff --git a/server/.sqlx/query-d951a84ad9d38b745f12217a31200667ca4d499d06b37e2ae9c36e65c0eb3c49.json b/server/.sqlx/query-d951a84ad9d38b745f12217a31200667ca4d499d06b37e2ae9c36e65c0eb3c49.json new file mode 100644 index 0000000..89cb878 --- /dev/null +++ b/server/.sqlx/query-d951a84ad9d38b745f12217a31200667ca4d499d06b37e2ae9c36e65c0eb3c49.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM reviews WHERE project=? AND id=?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "d951a84ad9d38b745f12217a31200667ca4d499d06b37e2ae9c36e65c0eb3c49" +} diff --git a/server/.sqlx/query-e53285109ec191077d218ec990020165f44b8432d9c2583c9f14cd7d7c2e37bb.json b/server/.sqlx/query-e53285109ec191077d218ec990020165f44b8432d9c2583c9f14cd7d7c2e37bb.json new file mode 100644 index 0000000..f04a93c --- /dev/null +++ b/server/.sqlx/query-e53285109ec191077d218ec990020165f44b8432d9c2583c9f14cd7d7c2e37bb.json @@ -0,0 +1,47 @@ +{ + "db_name": "MySQL", + "query": "SELECT id,state,owner FROM reviews WHERE project=? AND branch=?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "LongLong", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "char_set": 63, + "max_size": 20 + } + }, + { + "ordinal": 1, + "name": "state", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL | UNSIGNED", + "char_set": 63, + "max_size": 3 + } + }, + { + "ordinal": 2, + "name": "owner", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 512 + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "e53285109ec191077d218ec990020165f44b8432d9c2583c9f14cd7d7c2e37bb" +} diff --git a/server/common/src/git.rs b/server/common/src/git.rs index 74c3247..ac0bdb3 100644 --- a/server/common/src/git.rs +++ b/server/common/src/git.rs @@ -326,6 +326,12 @@ impl RepoData { .await } + async fn delete_branch(&self, repo: &Repository, branch: &str) -> Result<(), Error> { + let mut cmd = self.git_cmd(repo); + cmd.arg("branch").arg("--delete").arg("--force").arg(branch); + self.run(&mut cmd).await + } + async fn get_log_format( &self, repo: &Repository, @@ -564,4 +570,11 @@ impl Repository { data.get_commiter(self, commit.as_str()).await } + + pub async fn delete_branch(&self, branch: impl Into<String>) -> Result<(), Error> { + let branch = branch.into(); + let data = self.lock.read().await; + + data.delete_branch(self, branch.as_str()).await + } } diff --git a/server/common/src/tests.rs b/server/common/src/tests.rs index f08ca44..41f44fe 100644 --- a/server/common/src/tests.rs +++ b/server/common/src/tests.rs @@ -202,7 +202,7 @@ async fn git_get_author_commiter(repo: &git::Repository) { assert!(repo.get_author("<invalid>").await.is_err()); } -async fn git_fetch(bare: bool) { +async fn git_fetch(bare: bool) -> git::Repository { let path = testdir!().join("repo"); let remote_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/testdata/bare"); let remote = remote_path.to_string_lossy().into_owned(); @@ -231,6 +231,7 @@ async fn git_fetch(bare: bool) { branch.unwrap(); other.unwrap(); } + repo } #[tokio::test] @@ -266,3 +267,19 @@ async fn test_git_fetch() { async fn test_git_bare_fetch() { git_fetch(true).await; } + +#[tokio::test] +async fn test_git_delete_branch() { + // Using git_fetch as we need a writeable git repo + let repo = git_fetch(false).await; + assert!(repo.delete_branch("other").await.is_ok()); + assert!(repo.delete_branch("does-not-exist").await.is_err()); +} + +#[tokio::test] +async fn test_git_bare_delete_branch() { + // Using git_fetch as we need a writeable git repo + let repo = git_fetch(true).await; + assert!(repo.delete_branch("other").await.is_ok()); + assert!(repo.delete_branch("does-not-exist").await.is_err()); +} diff --git a/server/src/git_root.rs b/server/src/git_root.rs index 31e4d45..f818495 100644 --- a/server/src/git_root.rs +++ b/server/src/git_root.rs @@ -62,6 +62,20 @@ impl Roots { Ok(()) } + + pub async fn del_branch(&self, project_id: &str, branch: &str) -> Result<(), git::Error> { + let repo; + { + let data = self.data.lock().unwrap(); + if let Some(tmp_repo) = data.project_repo.get(project_id) { + repo = tmp_repo.clone(); + } else { + return Ok(()); + } + } + + repo.delete_branch(branch).await + } } #[derive(Debug)] diff --git a/server/src/main.rs b/server/src/main.rs index 019f28f..f07c372 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -46,6 +46,8 @@ struct Db(sqlx::MySqlPool); reviews, review, review_id, + review_del, + review_id_del, users, user_key_add, user_key_get, @@ -633,6 +635,114 @@ async fn get_review_from_id( Ok(Json(review)) } +#[allow(clippy::too_many_arguments)] +async fn del_review( + mut db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + reviewid: u64, + state: u8, + owner: &str, + branch: &str, +) -> Result<&'static str, Custom<&'static str>> { + let state = api_model::ReviewState::try_from(state).unwrap(); + + if owner != session.user_id + && project_check_maintainer(&mut db, session, projectid) + .await + .is_err() + { + return Err(Custom( + Status::Unauthorized, + "Not owner of review or maintainer of project", + )); + } + + match state { + api_model::ReviewState::Draft | api_model::ReviewState::Dropped => {} + api_model::ReviewState::Open | api_model::ReviewState::Closed => { + return Err(Custom(Status::BadRequest, "Review is open or closed")); + } + } + + roots + .del_branch(projectid, branch) + .map_err(|_| Custom(Status::InternalServerError, "git error")) + .await?; + + sqlx::query!( + "DELETE FROM reviews WHERE project=? AND id=?", + projectid, + reviewid + ) + .execute(&mut **db) + .await + .unwrap(); + + Ok("") +} + +async fn del_review_from_branch( + mut db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + branch: &str, +) -> Result<&'static str, Custom<&'static str>> { + let (id, state, owner) = sqlx::query!( + "SELECT id,state,owner FROM reviews WHERE project=? AND branch=?", + projectid, + branch + ) + .fetch_one(&mut **db) + .map_ok(|r| (r.id, r.state, r.owner)) + .map_err(|_| Custom(Status::NotFound, "No such review")) + .await?; + + del_review( + db, + roots, + session, + projectid, + id, + state, + owner.as_str(), + branch, + ) + .await +} + +async fn del_review_from_id( + mut db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + reviewid: u64, +) -> Result<&'static str, Custom<&'static str>> { + let (state, branch, owner) = sqlx::query!( + "SELECT state,branch,owner FROM reviews WHERE project=? AND id=?", + projectid, + reviewid + ) + .fetch_one(&mut **db) + .map_ok(|r| (r.state, r.branch, r.owner)) + .map_err(|_| Custom(Status::NotFound, "No such review")) + .await?; + + del_review( + db, + roots, + session, + projectid, + reviewid, + state, + owner.as_str(), + branch.as_str(), + ) + .await +} + #[utoipa::path( responses( (status = 200, description = "Get review", body = api_model::Review), @@ -665,6 +775,47 @@ async fn review_encoded_path( #[utoipa::path( responses( + (status = 200, description = "Review deleted"), + (status = 400, description = "Review is open or closed"), + (status = 401, description = "Not owner of review or maintainer of project"), + (status = 404, description = "No such review"), + ), + security( + ("session" = []), + ), +)] +#[delete("/review/<projectid>/<branch..>")] +async fn review_del( + db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + branch: PathBuf, +) -> Result<&'static str, Custom<&'static str>> { + del_review_from_branch( + db, + roots, + session, + projectid, + branch.as_path().to_str().unwrap(), + ) + .await +} + +// Backup for the above. Matches if <branch> ends up encoded (with / as %2f) +#[delete("/review/<projectid>/<branch>", rank = 1)] +async fn review_encoded_path_del( + db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + branch: &str, +) -> Result<&'static str, Custom<&'static str>> { + del_review_from_branch(db, roots, session, projectid, branch).await +} + +#[utoipa::path( + responses( (status = 200, description = "Get review", body = api_model::Review), (status = 404, description = "No such review"), ), @@ -684,6 +835,28 @@ async fn review_id( #[utoipa::path( responses( + (status = 200, description = "Remove deleted"), + (status = 400, description = "Review is open or closed"), + (status = 401, description = "Not owner of review or maintainer of project"), + (status = 404, description = "No such review"), + ), + security( + ("session" = []), + ), +)] +#[delete("/review/<projectid>?<reviewid>")] +async fn review_id_del( + db: Connection<Db>, + roots: &State<git_root::Roots>, + session: auth::Session, + projectid: &str, + reviewid: u64, +) -> Result<&'static str, Custom<&'static str>> { + del_review_from_id(db, roots, session, projectid, reviewid).await +} + +#[utoipa::path( + responses( (status = 200, description = "Get all users", body = api_model::Users), ), security( @@ -941,8 +1114,11 @@ fn rocket_from_config(figment: Figment) -> Rocket<Build> { project_user_del, reviews, review, + review_del, review_encoded_path, + review_encoded_path_del, review_id, + review_id_del, users, user_key_add, user_key_get, |
