feat: REST endpoint to post comment on gist

SUMMARY
    Comment on gist and  utilities to generate post comment REST
    endpoint route from username and gist public ID component

ERRORS
    - Gist doesn't exist, 404 GistNotFound is returned
    - Gist is private and commenting user is not ower, 404 GistNotFound
      is returned
    - Comment is empty, 400 EmptyComment is returned
master
Aravinth Manivannan 2022-02-18 23:52:21 +05:30
parent 91be25d9f6
commit b58a2fcee0
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
3 changed files with 151 additions and 5 deletions

View File

@ -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<PostCommentRequest>,
path: web::Path<PostCommentPath>,
id: Identity,
db: crate::DB,
) -> ServiceResult<impl Responder> {
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;
}
}

View File

@ -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

View File

@ -115,6 +115,9 @@ pub enum ServiceError {
#[display(fmt = "File System Error {}", _0)]
FSError(FSError),
#[display(fmt = "Comment is empty")]
EmptyComment,
}
impl From<CredsError> 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()