From bd74717e10fb36e19893c15941876b2383b94714 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Thu, 6 Feb 2025 00:05:57 +0100 Subject: Add DELETE command for review Only the owner or a maintainer of the project can remove a review. Removing a review also removes the git branch. Only reviews that are either draft or dropped can be removed. --- server/src/git_root.rs | 14 ++++ server/src/main.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) (limited to 'server/src') 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, + roots: &State, + 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, + roots: &State, + 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, + roots: &State, + 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), @@ -663,6 +773,47 @@ async fn review_encoded_path( get_review_from_branch(db, projectid, branch).await } +#[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//")] +async fn review_del( + db: Connection, + roots: &State, + 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 ends up encoded (with / as %2f) +#[delete("/review//", rank = 1)] +async fn review_encoded_path_del( + db: Connection, + roots: &State, + 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), @@ -682,6 +833,28 @@ async fn review_id( get_review_from_id(db, projectid, reviewid).await } +#[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/?")] +async fn review_id_del( + db: Connection, + roots: &State, + 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), @@ -941,8 +1114,11 @@ fn rocket_from_config(figment: Figment) -> Rocket { 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, -- cgit v1.2.3-70-g09d2