feat: REST endpoint to delete comment by ID

DESCRIPTION
    Each comment is uniquely identified by database assigned, serially
    incremented ID. Access controlled REST endpoint is added to delete
    comment by ID.

ERRORS RETURNED
    - Gist doesn't exist: 404 GistNotFound
    - Gist is private and requesting user is not owner or is not visible
      to user: 404 GistNotFound
    - Gist exists and is visible to requesting user but comment doesn't
      exist: 404 CommentNotFound
    - Gist exists and is visible to requesting user is not comment owner
      : 401 UnauthorizedOperation
master
Aravinth Manivannan 2 years ago
parent 3af7c7c0f8
commit 604d887164
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88

@ -50,6 +50,7 @@ pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(post_comment);
cfg.service(get_comment);
cfg.service(get_gist_comments);
cfg.service(delete_comment);
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -212,6 +213,33 @@ async fn get_gist_comments(
}
}
#[my_codegen::delete(
path = "crate::V1_API_ROUTES.gist.delete_comment",
wrap = "super::get_auth_middleware()"
)]
async fn delete_comment(
path: web::Path<GetCommentPath>,
id: Identity,
db: crate::DB,
) -> ServiceResult<impl Responder> {
let gist = db.get_gist(&path.gist).await?;
let comment = db.get_comment_by_id(path.comment_id).await?;
let username = id.identity().unwrap();
if username != comment.owner {
match gist.visibility {
GistVisibility::Public | GistVisibility::Unlisted => {
Err(ServiceError::UnauthorizedOperation(
"This user is not the owner of the comment to delete it".into(),
))
}
GistVisibility::Private => Err(ServiceError::GistNotFound),
}
} else {
db.delete_comment(&username, comment.id).await?;
Ok(HttpResponse::Ok())
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -684,5 +712,72 @@ mod tests {
);
}
}
/*
* +++++++++++++++++++++++++++++++++++
* DELETE COMMENT
* +++++++++++++++++++++++++++++++++++
*/
let mut delete_comment_component = GetCommentPath {
gist: "gistdoesntexist".into(),
username: NAME.into(),
comment_id: 34234234,
};
println!("delete comments; unauthenticated, gist does't exist");
let del_comment_path = V1_API_ROUTES
.gist
.get_delete_comment_route(&delete_comment_component);
let resp = delete_request!(&app, &del_comment_path);
assert_eq!(resp.status(), StatusCode::FOUND);
println!("delete comments; authenticated, gist does't exist");
let resp = delete_request!(&app, &del_comment_path, cookies.clone());
assert_eq!(resp.status(), ServiceError::GistNotFound.status_code());
let err: ErrorToResponse = test::read_body_json(resp).await;
assert_eq!(err.error, format!("{}", ServiceError::GistNotFound));
println!("delete comments; authenticated, comment doesn't exist");
delete_comment_component.gist = gist_id.clone();
let del_comment_path = V1_API_ROUTES
.gist
.get_delete_comment_route(&delete_comment_component);
let resp = delete_request!(&app, &del_comment_path, cookies.clone());
assert_eq!(resp.status(), ServiceError::CommentNotFound.status_code());
let err: ErrorToResponse = test::read_body_json(resp).await;
assert_eq!(err.error, format!("{}", ServiceError::CommentNotFound));
println!("delete comments; authenticated but comment_owner != user and gist is public");
delete_comment_component.gist = gist_id.clone();
delete_comment_component.comment_id = comment_ids.get(0).as_ref().unwrap().0.id;
let del_comment_path = V1_API_ROUTES
.gist
.get_delete_comment_route(&delete_comment_component);
let resp = delete_request!(&app, &del_comment_path, cookies2.clone());
assert_eq!(
resp.status(),
ServiceError::UnauthorizedOperation("".into()).status_code()
);
let err: ErrorToResponse = test::read_body_json(resp).await;
assert!(err.error.contains(&format!(
"{}",
ServiceError::UnauthorizedOperation("".into())
)));
println!("delete comments; authenticated but comment_owner != user and gist is private");
delete_comment_component.gist = private.clone();
delete_comment_component.comment_id = comment_ids.last().as_ref().unwrap().0.id;
let del_comment_path = V1_API_ROUTES
.gist
.get_delete_comment_route(&delete_comment_component);
let resp = delete_request!(&app, &del_comment_path, cookies2.clone());
assert_eq!(resp.status(), ServiceError::GistNotFound.status_code());
let err: ErrorToResponse = test::read_body_json(resp).await;
assert_eq!(err.error, format!("{}", ServiceError::GistNotFound));
println!("delete comments; authenticated comment_owner == owner");
let resp = delete_request!(&app, &del_comment_path, cookies.clone());
assert_eq!(resp.status(), StatusCode::OK);
}
}

@ -77,6 +77,8 @@ pub struct Gist {
pub get_comment: &'static str,
/// get gist comments
pub get_gist_comments: &'static str,
/// delete comment
pub delete_comment: &'static str,
}
impl Gist {
@ -86,6 +88,7 @@ impl Gist {
let get_file = "/api/v1/gist/profile/{username}/{gist}/contents/{file}";
let post_comment = "/api/v1/gist/profile/{username}/{gist}/comments";
let get_comment = "/api/v1/gist/profile/{username}/{gist}/comment/{comment_id}";
let delete_comment = get_comment;
let get_gist_comments = post_comment;
Gist {
new,
@ -93,6 +96,7 @@ impl Gist {
post_comment,
get_comment,
get_gist_comments,
delete_comment,
}
}
@ -117,13 +121,18 @@ impl Gist {
self.get_post_comment_route(components)
}
/// get post_comment route with placeholders replaced with values provided.
/// get get_comment route with placeholders replaced with values provided.
pub fn get_get_comment_route(&self, components: &GetCommentPath) -> String {
self.get_comment
.replace("{username}", &components.username)
.replace("{gist}", &components.gist)
.replace("{comment_id}", &components.comment_id.to_string())
}
/// get delete_comment route with placeholders replaced with values provided.
pub fn get_delete_comment_route(&self, components: &GetCommentPath) -> String {
self.get_get_comment_route(components)
}
}
/// Account management routes
@ -225,6 +234,7 @@ mod tests {
let post_comment = format!("/api/v1/gist/profile/{NAME}/{GIST}/comments");
let get_gist_comments = format!("/api/v1/gist/profile/{NAME}/{GIST}/comments");
let get_comment = format!("/api/v1/gist/profile/{NAME}/{GIST}/comment/{COMMENT_ID}");
let delete_comment = format!("/api/v1/gist/profile/{NAME}/{GIST}/comment/{COMMENT_ID}");
let get_file_component = GetFilePath {
file: FILE.into(),
@ -260,5 +270,15 @@ mod tests {
get_gist_comments,
ROUTES.gist.get_post_comment_route(&get_gist_comments_path)
);
let delete_comment_path = GetCommentPath {
gist: GIST.into(),
username: NAME.into(),
comment_id: COMMENT_ID,
};
assert_eq!(
delete_comment,
ROUTES.gist.get_delete_comment_route(&delete_comment_path)
);
}
}

Loading…
Cancel
Save