diff --git a/src/api/v1/gists.rs b/src/api/v1/gists.rs index 368fe28..8949def 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; +use super::routes::{GetFilePath, PostCommentPath}; use crate::data::api::v1::gists::{CreateGist, FileInfo, GistID}; use crate::errors::*; use crate::utils::escape_spaces; @@ -47,6 +47,7 @@ impl CreateGistRequest { pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(new); cfg.service(get_file); + cfg.service(post_comment); } #[my_codegen::post( @@ -111,6 +112,42 @@ async fn get_file( } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PostCommentRequest { + pub comment: String, +} + +#[my_codegen::post( + path = "crate::V1_API_ROUTES.gist.post_comment", + wrap = "super::get_auth_middleware()" +)] +async fn post_comment( + payload: web::Json, + path: web::Path, + id: Identity, + db: crate::DB, +) -> ServiceResult { + let comment = payload.comment.trim(); + if comment.is_empty() { + return Err(ServiceError::EmptyComment); + } + + let username = id.identity().unwrap(); + let gist = db.get_gist(&path.gist).await?; + if gist.visibility == GistVisibility::Private && username != gist.owner { + return Err(ServiceError::GistNotFound); + } + + let msg = CreateGistComment { + owner: &username, + gist_public_id: &path.gist, + comment: &payload.comment, + }; + + db.new_comment(&msg).await?; // TODO get comment ID + Ok(HttpResponse::Ok().finish()) +} + #[cfg(test)] mod tests { use super::*; @@ -300,14 +337,14 @@ mod tests { .await; assert_eq!(create_gist_resp.status(), StatusCode::TEMPORARY_REDIRECT); - let unlisted = create_gist_resp + let private = create_gist_resp .headers() .get(http::header::LOCATION) .unwrap() .to_str() .unwrap(); - get_file_path.gist = unlisted.into(); + get_file_path.gist = private.into(); for file in one_file.iter() { get_file_path.file = file.filename.clone(); let path = V1_API_ROUTES.gist.get_file_route(&get_file_path); @@ -335,5 +372,89 @@ mod tests { let txt: ErrorToResponse = test::read_body_json(resp).await; assert_eq!(txt.error, format!("{}", ServiceError::GistNotFound)); } + + println!("testing comments"); + let mut create_comment = PostCommentPath { + username: NAME2.into(), + gist: gist_id.into(), + }; + + let mut comment = PostCommentRequest { comment: "".into() }; + println!("empty comment"); + // empty comment + data.bad_post_req_test( + &db, + NAME, + PASSWORD, + V1_API_ROUTES.gist.post_comment, + &comment, + ServiceError::EmptyComment, + ) + .await; + + comment.comment = "foo".into(); + + create_comment.gist = "gistnotexist".into(); + let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); + println!("gist not found"); + data.bad_post_req_test( + &db, + NAME, + PASSWORD, + V1_API_ROUTES.gist.post_comment, + &comment, + ServiceError::GistNotFound, + ) + .await; + + println!("comment OK"); + create_comment.gist = gist_id.into(); + let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); + let resp = test::call_service( + &app, + post_request!(&comment, &post_comment_path) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + + println!("comment OK"); + create_comment.gist = unlisted.into(); + let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); + let resp = test::call_service( + &app, + post_request!(&comment, &post_comment_path) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + + println!("comment OK"); + create_comment.gist = private.into(); + let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); + let resp = test::call_service( + &app, + post_request!(&comment, &post_comment_path) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + + // commenting on private gist + println!("private gist, not OK"); + create_comment.gist = private.into(); + let post_comment_path = V1_API_ROUTES.gist.get_post_comment_route(&create_comment); + data.bad_post_req_test( + &db, + NAME2, + PASSWORD, + &post_comment_path, + &comment, + ServiceError::GistNotFound, + ) + .await; } } diff --git a/src/api/v1/routes.rs b/src/api/v1/routes.rs index a7fb4cf..348ec36 100644 --- a/src/api/v1/routes.rs +++ b/src/api/v1/routes.rs @@ -52,13 +52,22 @@ pub struct GetFilePath { pub file: String, } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PostCommentPath { + pub username: String, + pub gist: String, +} + /// Authentication routes pub struct Gist { /// logout route pub new: &'static str, - /// get fie route + /// get flie route pub get_file: &'static str, + + /// post comment on gist + pub post_comment: &'static str, } impl Gist { @@ -66,7 +75,12 @@ impl Gist { pub const fn new() -> Gist { let new = "/api/v1/gist/new"; let get_file = "/api/v1/gist/profile/{username}/{gist}/contents/{file}"; - Gist { new, get_file } + let post_comment = "/api/v1/gist/profile/{username}/{gist}/comments"; + Gist { + new, + get_file, + post_comment, + } } /// get file routes with placeholders replaced with values provided. @@ -77,6 +91,13 @@ impl Gist { .replace("{gist}", &components.gist) .replace("{file}", &urlencoding::encode(&components.file)) } + + /// get post_comment route with placeholders replaced with values provided. + pub fn get_post_comment_route(&self, components: &PostCommentPath) -> String { + self.post_comment + .replace("{username}", &components.username) + .replace("{gist}", &components.gist) + } } /// Account management routes diff --git a/src/errors.rs b/src/errors.rs index 93fbfce..f1ce17c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -115,6 +115,9 @@ pub enum ServiceError { #[display(fmt = "File System Error {}", _0)] FSError(FSError), + + #[display(fmt = "Comment is empty")] + EmptyComment, } impl From for ServiceError { @@ -220,6 +223,7 @@ impl ResponseError for ServiceError { ServiceError::GistNotFound => 404, ServiceError::CommentNotFound => 404, + ServiceError::EmptyComment => 400, }; StatusCode::from_u16(status_code).unwrap()