diff options
| author | Santo Cariotti <santo@dcariotti.me> | 2021-03-17 14:16:22 +0100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-17 14:16:22 +0100 | 
| commit | 5691c3e7c44b833b4d81c0080ad8179192259652 (patch) | |
| tree | 9749fbf7ff0b9c887df5439aef793bc31c7f202e | |
| parent | 896ab2a9fb4a34d82c70792a1114ac1a0f4ad6c0 (diff) | |
| parent | 904c7e40a2d092eea87ca6d9ea80edce8a2aad2a (diff) | |
Merge pull request #14 from gico-net/feat/add-commits
Add "Commit"'s endpoints
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | schema.sql | 20 | ||||
| -rw-r--r-- | src/commit/mod.rs | 2 | ||||
| -rw-r--r-- | src/commit/models.rs | 96 | ||||
| -rw-r--r-- | src/commit/routes.rs | 84 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/repository/routes.rs | 2 | 
7 files changed, 196 insertions, 12 deletions
| @@ -22,7 +22,7 @@ slog-async = "2.4.0"  dotenv = "0.15.0"  config = "0.10.1"  serde = { version = "1.0.104", features = ["derive"] } -chrono = { version = "0.4.19", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"] }  uuid = { version = "0.8.2", features = ["serde", "v4"] }  regex = "1"  md-5 = "0.9.1" @@ -1,9 +1,9 @@  CREATE TABLE "repository" (      id uuid PRIMARY KEY NOT NULL,      url varchar(255) UNIQUE NOT NULL, -    created_at timestamp NOT NULL, -    updated_at timestamp NOT NULL, -    uploader_ip varchar(15) NOT NULL +    created_at timestamp NOT NULL DEFAULT NOW(), +    updated_at timestamp NOT NULL DEFAULT NOW(), +    uploader_ip varchar(21) NOT NULL  );  CREATE TABLE "email"( @@ -13,19 +13,19 @@ CREATE TABLE "email"(  CREATE TABLE "commit" (      hash varchar(40) PRIMARY KEY NOT NULL, -    tree varchar(40) REFERENCES commit(hash) NULL, +    tree varchar(40) REFERENCES commit(hash) ON DELETE CASCADE NULL,      text text NOT NULL, -    date timestamp NOT NULL, -    author_email varchar(120) REFERENCES email(email) NOT NULL, +    date timestamptz NOT NULL, +    author_email varchar(120) REFERENCES email(email) ON DELETE NO ACTION NOT NULL,      author_name varchar(120) NOT NULL, -    committer_email varchar(120) REFERENCES email(email) NOT NULL, +    committer_email varchar(120) REFERENCES email(email) ON DELETE NO ACTION NOT NULL,      committer_name varchar(120) NOT NULL, -    repository_url varchar(256) REFERENCES repository(url) NOT NULL  +    repository_url varchar(256) REFERENCES repository(url) ON DELETE CASCADE NOT NULL  );  CREATE TABLE "branch" (      id uuid PRIMARY KEY NOT NULL,      name varchar(120) NOT NULL, -    repository_id uuid REFERENCES repository(id) NOT NULL, -    head varchar(40) REFERENCES commit(hash) NULL +    repository_id uuid REFERENCES repository(id) ON DELETE CASCADE NOT NULL, +    head varchar(40) REFERENCES commit(hash) ON DELETE SET NULL NULL  ); diff --git a/src/commit/mod.rs b/src/commit/mod.rs new file mode 100644 index 0000000..a0e1883 --- /dev/null +++ b/src/commit/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod routes; diff --git a/src/commit/models.rs b/src/commit/models.rs new file mode 100644 index 0000000..8b81a15 --- /dev/null +++ b/src/commit/models.rs @@ -0,0 +1,96 @@ +use crate::db::get_client; +use crate::errors::{AppError, AppErrorType}; + +use chrono::{DateTime, Local}; +use deadpool_postgres::Pool; +use serde::{Deserialize, Serialize}; +use tokio_pg_mapper::FromTokioPostgresRow; +use tokio_pg_mapper_derive::PostgresMapper; + +#[derive(Serialize, Deserialize, PostgresMapper)] +#[pg_mapper(table = "commit")] +/// Commit model +pub struct Commit { +    pub hash: String, +    pub tree: Option<String>, +    pub text: String, +    pub date: DateTime<Local>, +    pub author_email: String, // Reference to Email +    pub author_name: String, +    pub committer_email: String, // Reference to Email +    pub committer_name: String, +    pub repository_url: String, // Reference to Repository +} + +impl Commit { +    /// Find all commits. Order them by descrescent `date` field +    pub async fn find_all(pool: Pool) -> Result<Vec<Commit>, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client +            .prepare("SELECT * FROM commit ORDER BY date DESC") +            .await?; + +        let commits = client +            .query(&statement, &[]) +            .await? +            .iter() +            .map(|row| Commit::from_row_ref(row).unwrap()) +            .collect::<Vec<Commit>>(); + +        Ok(commits) +    } + +    // Find a commit that it has an hash equals to `hash` +    pub async fn find(pool: Pool, hash: String) -> Result<Commit, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client +            .prepare("SELECT * FROM commit WHERE hash = $1") +            .await?; + +        let commit = client +            .query_opt(&statement, &[&hash]) +            .await? +            .map(|row| Commit::from_row_ref(&row).unwrap()); + +        match commit { +            Some(commit) => Ok(commit), +            None => Err(AppError { +                error_type: AppErrorType::NotFoundError, +                cause: None, +                message: Some("Commit not found".to_string()), +            }), +        } +    } + +    /// Find a commit and delete it, but before check if "Authorization" +    /// matches with SECRET_KEY +    pub async fn delete( +        pool: Pool, +        hash: &String, +    ) -> Result<Commit, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client +            .prepare( +                " +                DELETE FROM commit +                WHERE hash=$1 +                RETURNING * +                ", +            ) +            .await?; + +        let commit = client +            .query_opt(&statement, &[&hash]) +            .await? +            .map(|row| Commit::from_row_ref(&row).unwrap()); + +        match commit { +            Some(commit) => Ok(commit), +            None => Err(AppError { +                error_type: AppErrorType::NotFoundError, +                cause: None, +                message: Some("Commit not found".to_string()), +            }), +        } +    } +} diff --git a/src/commit/routes.rs b/src/commit/routes.rs new file mode 100644 index 0000000..e49f698 --- /dev/null +++ b/src/commit/routes.rs @@ -0,0 +1,84 @@ +use crate::commit::models::Commit; +use crate::config::AppState; +use crate::errors::{AppError, AppErrorResponse, AppErrorType}; +use actix_web::http::header; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use slog::info; +use std::env; + +/// Endpoint used for getting all commits +async fn index(state: web::Data<AppState>) -> impl Responder { +    info!(state.log, "GET /commit/"); +    let result = Commit::find_all(state.pool.clone()).await; + +    match result { +        Ok(commits) => HttpResponse::Ok().json(commits), +        _ => HttpResponse::BadRequest().json(AppErrorResponse { +            detail: "Error trying to read all commits from database" +                .to_string(), +        }), +    } +} + +// Endpoint used for getting one commit +async fn get_commit( +    state: web::Data<AppState>, +    hash: web::Path<(String,)>, +) -> impl Responder { +    info!(state.log, "GET /commit/{}/", &hash.0); + +    let result = Commit::find(state.pool.clone(), hash.0.clone()).await; + +    result +        .map(|commit| HttpResponse::Ok().json(commit)) +        .map_err(|e| e) +} + +/// Endpoint used for delete commitsitory. +/// It uses a SECRET_KEY used like an API key +async fn delete_commit( +    req: HttpRequest, +    state: web::Data<AppState>, +    hash: web::Path<(String,)>, +) -> impl Responder { +    match req.headers().get(header::AUTHORIZATION) { +        Some(x) +            if x.to_str().unwrap() +                != env::var("SECRET_KEY").unwrap_or("".to_string()) => +        { +            info!(state.log, "DELETE /commit/{}/ 401", &hash.0); +            return Err(AppError { +                error_type: AppErrorType::AuthorizationError, +                message: Some( +                    "You must provide a valid Authorization".to_string(), +                ), +                cause: None, +            }); +        } +        Some(_) => {} +        None => { +            info!(state.log, "DELETE /commit/{}/ 400", &hash.0); +            return Ok(HttpResponse::BadRequest().body("")); +        } +    }; + +    let result = Commit::delete(state.pool.clone(), &hash.0).await; +    info!(state.log, "DELETE /commit/{}/", &hash.0); + +    result +        .map(|_| HttpResponse::NoContent().body("")) +        .map_err(|e| e) +} + +/// Routes for commits +pub fn config(cfg: &mut web::ServiceConfig) { +    cfg.service( +        web::scope("/commit") +            .service(web::resource("{_:/?}").route(web::get().to(index))) +            .service( +                web::resource("/{hash}{_:/?}") +                    .route(web::get().to(get_commit)) +                    .route(web::delete().to(delete_commit)), +            ), +    ); +} diff --git a/src/main.rs b/src/main.rs index 63f48fa..b2e0ba3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod db;  mod errors;  mod helpers; +mod commit;  mod email;  mod repository; @@ -37,6 +38,7 @@ async fn main() -> std::io::Result<()> {              .wrap(middleware::Logger::default())              .configure(repository::routes::config)              .configure(email::routes::config) +            .configure(commit::routes::config)      })      .bind(format!("{}:{}", config.server.host, config.server.port))?      .run() diff --git a/src/repository/routes.rs b/src/repository/routes.rs index abf77b6..32443b1 100644 --- a/src/repository/routes.rs +++ b/src/repository/routes.rs @@ -93,7 +93,7 @@ async fn create_repo(              .await;      result -        .map(|repo| HttpResponse::Ok().json(repo)) +        .map(|repo| HttpResponse::Created().json(repo))          .map_err(|e| e)  } | 
