From 7cdfcc47bb67ef93fa8b443950d39e7ef0946191 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Tue, 15 Feb 2022 23:48:05 +0530 Subject: [PATCH] feat: HTTP JSON endpoint to create new gist --- src/api/v1/gists.rs | 137 +++++++++++++++++++++++++++++++++++++++++++ src/api/v1/mod.rs | 2 + src/api/v1/routes.rs | 17 +++++- 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/api/v1/gists.rs diff --git a/src/api/v1/gists.rs b/src/api/v1/gists.rs new file mode 100644 index 0000000..ac972f6 --- /dev/null +++ b/src/api/v1/gists.rs @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use actix_identity::Identity; +use actix_web::*; + +use db_core::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::data::api::v1::gists::{CreateGist, File, GistID}; +use crate::errors::*; +use crate::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateGistRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + pub visibility: GistVisibility, + pub files: Vec, +} + +impl CreateGistRequest { + pub fn to_create_gist<'a>(&'a self, owner: &'a str) -> CreateGist<'a> { + CreateGist { + owner, + description: self.description.as_ref().map(|s| s.as_str()), + visibility: &self.visibility, + } + } +} + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(new); +} + +#[my_codegen::post( + path = "crate::V1_API_ROUTES.gist.new", + wrap = "super::get_auth_middleware()" +)] +async fn new( + payload: web::Json, + data: AppData, + id: Identity, + db: crate::DB, +) -> ServiceResult { + let username = id.identity().unwrap(); + let mut gist = data + .new_gist(db.as_ref(), &payload.to_create_gist(&username)) + .await?; + data.write_file( + db.as_ref(), + GistID::Repository(&mut gist.repository), + &payload.files, + ) + .await?; + Ok(HttpResponse::TemporaryRedirect() + .insert_header((http::header::LOCATION, gist.id)) + .finish()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::*; + + #[actix_rt::test] + async fn test_new_gist_works() { + let config = [ + sqlx_postgres::get_data().await, + sqlx_sqlite::get_data().await, + ]; + + for (db, data) in config.iter() { + const NAME: &str = "httpgisttestuser"; + const EMAIL: &str = "httpgisttestuser@sss.com"; + const PASSWORD: &str = "longpassword2"; + + let _ = futures::join!(data.delete_user(db, NAME, PASSWORD),); + + let (_creds, signin_resp) = data.register_and_signin(db, NAME, EMAIL, PASSWORD).await; + let cookies = get_cookie!(signin_resp); + let app = get_app!(data, db).await; + + let files = [ + File { + filename: "foo".into(), + content: "foobar".into(), + }, + File { + filename: "bar".into(), + content: "foobar".into(), + }, + File { + filename: "foo bar".into(), + content: "foobar".into(), + }, + ]; + + let create_gist_msg = CreateGistRequest { + description: None, + visibility: GistVisibility::Public, + files: files.to_vec(), + }; + + let create_gist_resp = test::call_service( + &app, + post_request!(&create_gist_msg, V1_API_ROUTES.gist.new) + .cookie(cookies) + .to_request(), + ) + .await; + assert_eq!(create_gist_resp.status(), StatusCode::TEMPORARY_REDIRECT); + + let gist_id = create_gist_resp + .headers() + .get(http::header::LOCATION) + .unwrap() + .to_str() + .unwrap(); + data.gist_created_test_helper(db, gist_id, NAME).await; + data.gist_files_written_helper(db, gist_id, &files).await; + } + } +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index fae3917..207a59d 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -20,6 +20,7 @@ use serde::Deserialize; pub mod account; pub mod auth; +pub mod gists; pub mod meta; pub mod routes; @@ -29,6 +30,7 @@ pub fn services(cfg: &mut ServiceConfig) { auth::services(cfg); account::services(cfg); meta::services(cfg); + gists::services(cfg); } #[derive(Deserialize)] diff --git a/src/api/v1/routes.rs b/src/api/v1/routes.rs index a7c7e64..419d5dc 100644 --- a/src/api/v1/routes.rs +++ b/src/api/v1/routes.rs @@ -45,6 +45,19 @@ impl Auth { } } +/// Authentication routes +pub struct Gist { + /// logout route + pub new: &'static str, +} +impl Gist { + /// create new instance of Authentication route + pub const fn new() -> Gist { + let new = "/api/v1/gist/new"; + Gist { new } + } +} + /// Account management routes pub struct Account { /// delete account route @@ -95,9 +108,10 @@ pub struct Routes { pub auth: Auth, /// Account routes pub account: Account, - /// Meta routes pub meta: Meta, + /// Gist routes + pub gist: Gist, } impl Routes { @@ -107,6 +121,7 @@ impl Routes { auth: Auth::new(), account: Account::new(), meta: Meta::new(), + gist: Gist::new(), } } }