From f8b9172b88b388aa74d19601d4a999a2a4a52248 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 19 Feb 2022 22:29:26 +0530 Subject: [PATCH] feat: REST endpoint to get comment by ID DESCRIPTION Each comment is uniquely identified by database assigned, serially incremented ID. Access controlled but optionally authenticated HTTP REST endpoint is added to get comment by ID. ERRORS RETURNED - Gist doesn't exist: 404 GistNotFound - Gist is private and requesting user is not ower or is not authenticated: 404 GistNotFound - Comment is empty: 400 EmptyComment - Gist exists and is visible to requesting user but comment doesn't exist: 404 CommentNotFound --- src/api/v1/gists.rs | 135 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 7 deletions(-) diff --git a/src/api/v1/gists.rs b/src/api/v1/gists.rs index 9db8434..6d8c9ad 100644 --- a/src/api/v1/gists.rs +++ b/src/api/v1/gists.rs @@ -20,7 +20,7 @@ use actix_web::*; use db_core::prelude::*; use serde::{Deserialize, Serialize}; -use super::routes::{GetFilePath, PostCommentPath}; +use super::routes::{GetCommentPath, GetFilePath, PostCommentPath}; use crate::data::api::v1::gists::{CreateGist, FileInfo, GistID}; use crate::errors::*; use crate::utils::escape_spaces; @@ -48,6 +48,7 @@ pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(new); cfg.service(get_file); cfg.service(post_comment); + cfg.service(get_comment); } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -160,11 +161,37 @@ async fn post_comment( Ok(HttpResponse::Ok().json(resp)) } +#[my_codegen::get(path = "crate::V1_API_ROUTES.gist.get_comment")] +async fn get_comment( + path: web::Path, + id: Identity, + db: crate::DB, +) -> ServiceResult { + let gist = db.get_gist(&path.gist).await?; + + match gist.visibility { + GistVisibility::Public | GistVisibility::Unlisted => { + let comment = db.get_comment_by_id(path.comment_id).await?; + Ok(HttpResponse::Ok().json(comment)) + } + GistVisibility::Private => { + if let Some(username) = id.identity() { + if gist.owner == username { + let comment = db.get_comment_by_id(path.comment_id).await?; + return Ok(HttpResponse::Ok().json(comment)); + } + }; + Err(ServiceError::GistNotFound) + } + } +} + #[cfg(test)] mod tests { use super::*; use crate::data::api::v1::gists::{ContentType, FileType}; use crate::tests::*; + use actix_web::ResponseError; use crate::utils::escape_spaces; #[actix_rt::test] @@ -410,10 +437,10 @@ mod tests { ) .await; - let mut comment_ids = Vec::with_capacity(2); + let mut comment_ids = Vec::with_capacity(3); println!("comment OK"); - create_comment.gist = gist_id; + create_comment.gist = gist_id.clone(); let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); let resp = test::call_service( &app, @@ -424,10 +451,15 @@ mod tests { .await; assert_eq!(resp.status(), StatusCode::OK); let comment_resp: PostCommentResp = test::read_body_json(resp).await; - comment_ids.push(comment_resp); + comment_ids.push(( + comment_resp, + comment.clone(), + gist_id.clone(), + GistVisibility::Public, + )); println!("comment OK"); - create_comment.gist = unlisted; + create_comment.gist = unlisted.clone(); let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); let resp = test::call_service( &app, @@ -438,7 +470,12 @@ mod tests { .await; assert_eq!(resp.status(), StatusCode::OK); let comment_resp: PostCommentResp = test::read_body_json(resp).await; - comment_ids.push(comment_resp); + comment_ids.push(( + comment_resp, + comment.clone(), + unlisted.clone(), + GistVisibility::Unlisted, + )); println!("comment OK"); create_comment.gist = private.clone(); @@ -451,7 +488,13 @@ mod tests { ) .await; assert_eq!(resp.status(), StatusCode::OK); - let _comment_resp: PostCommentResp = test::read_body_json(resp).await; + let comment_resp: PostCommentResp = test::read_body_json(resp).await; + comment_ids.push(( + comment_resp, + comment.clone(), + private.clone(), + GistVisibility::Private, + )); // commenting on private gist println!("private gist, not OK"); @@ -466,5 +509,83 @@ mod tests { ServiceError::GistNotFound, ) .await; + + /* + * +++++++++++++++++++++++++++++++++++ + * GET COMMENT + * +++++++++++++++++++++++++++++++++++ + */ + + // gist not found + let mut get_comment_path_component = GetCommentPath { + gist: "gistdoesntexist".into(), + username: NAME.into(), + comment_id: 466767, + }; + let get_comment_path = V1_API_ROUTES + .gist + .get_get_comment_route(&get_comment_path_component); + println!("getting comment; gist doesn't exist"); + let resp = get_request!(&app, &get_comment_path); + assert_eq!(resp.status(), ServiceError::GistNotFound.status_code()); + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + assert_eq!(resp_err.error, format!("{}", ServiceError::GistNotFound)); + + // private gist + get_comment_path_component.gist = private.clone(); + let get_comment_path = V1_API_ROUTES + .gist + .get_get_comment_route(&get_comment_path_component); + println!("getting comment; private gist"); + let resp = get_request!(&app, &get_comment_path); + assert_eq!(resp.status(), ServiceError::GistNotFound.status_code()); + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + assert_eq!(resp_err.error, format!("{}", ServiceError::GistNotFound)); + + // comment not found + get_comment_path_component.gist = gist_id.clone(); + let get_comment_path = V1_API_ROUTES + .gist + .get_get_comment_route(&get_comment_path_component); + println!("getting comment; comment doesn't exist"); + let resp = get_request!(&app, &get_comment_path); + assert_eq!(resp.status(), ServiceError::CommentNotFound.status_code()); + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + assert_eq!(resp_err.error, format!("{}", ServiceError::CommentNotFound)); + + for (comment_id, comment_payload, gist, visibility) in comment_ids.iter() { + let component = GetCommentPath { + gist: gist.into(), + username: NAME.into(), + comment_id: comment_id.id, + }; + + if visibility == &GistVisibility::Private { + println!("getting comment; private gist but user==owner"); + let path = V1_API_ROUTES.gist.get_get_comment_route(&component); + let resp = get_request!(&app, &path, cookies.clone()); + assert_eq!(resp.status(), StatusCode::OK); + let comment: GistComment = test::read_body_json(resp).await; + assert_eq!(comment.comment, comment_payload.comment); + + println!("getting comment; private gist but user is unauthenticated"); + let resp = get_request!(&app, &path); + assert_eq!(resp.status(), ServiceError::GistNotFound.status_code()); + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + assert_eq!(resp_err.error, format!("{}", ServiceError::GistNotFound)); + + println!("getting comment; private gist but user != owner"); + let resp = get_request!(&app, &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)); + } else { + let path = V1_API_ROUTES.gist.get_get_comment_route(&component); + let resp = get_request!(&app, &path, cookies.clone()); + assert_eq!(resp.status(), StatusCode::OK); + let comment: GistComment = test::read_body_json(resp).await; + assert_eq!(comment.comment, comment_payload.comment); + } + } } }