diff options
Diffstat (limited to 'src/branch')
| -rw-r--r-- | src/branch/mod.rs | 2 | ||||
| -rw-r--r-- | src/branch/models.rs | 148 | ||||
| -rw-r--r-- | src/branch/routes.rs | 110 | 
3 files changed, 260 insertions, 0 deletions
| diff --git a/src/branch/mod.rs b/src/branch/mod.rs new file mode 100644 index 0000000..a0e1883 --- /dev/null +++ b/src/branch/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod routes; diff --git a/src/branch/models.rs b/src/branch/models.rs new file mode 100644 index 0000000..c8a8d65 --- /dev/null +++ b/src/branch/models.rs @@ -0,0 +1,148 @@ +use crate::db::get_client; +use crate::errors::{AppError, AppErrorType}; + +use deadpool_postgres::Pool; +use serde::{Deserialize, Serialize}; +use tokio_pg_mapper::FromTokioPostgresRow; +use tokio_pg_mapper_derive::PostgresMapper; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, PostgresMapper)] +#[pg_mapper(table = "branch")] +/// Branch model +pub struct Branch { +    pub id: Uuid, +    pub name: String, +    pub repository_id: Uuid, +    pub head: String, +} + +/// Struct used for forms +pub struct BranchData { +    pub name: String, +    pub repository_id: Uuid, +    pub head: String, +} + +impl Branch { +    /// Find all branches +    pub async fn find_all(pool: Pool) -> Result<Vec<Branch>, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client.prepare("SELECT * FROM branch").await?; + +        let branches = client +            .query(&statement, &[]) +            .await? +            .iter() +            .map(|row| Branch::from_row_ref(row).unwrap()) +            .collect::<Vec<Branch>>(); + +        Ok(branches) +    } + +    /// Find a branch with an `id` equals to an Uuid element +    pub async fn find(pool: Pool, id: &Uuid) -> Result<Branch, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = +            client.prepare("SELECT * FROM branch WHERE id = $1").await?; + +        let branch = client +            .query_opt(&statement, &[&id]) +            .await? +            .map(|row| Branch::from_row_ref(&row).unwrap()); + +        match branch { +            Some(branch) => Ok(branch), +            None => Err(AppError { +                error_type: AppErrorType::NotFoundError, +                cause: None, +                message: Some("Branch not found".to_string()), +            }), +        } +    } + +    /// Find all branches of a repository +    pub async fn find_by_repo( +        pool: Pool, +        repo: &Uuid, +    ) -> Result<Vec<Branch>, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client +            .prepare("SELECT * FROM branch WHERE repository_id=$1") +            .await?; + +        let branches = client +            .query(&statement, &[&repo]) +            .await? +            .iter() +            .map(|row| Branch::from_row_ref(row).unwrap()) +            .collect::<Vec<Branch>>(); + +        Ok(branches) +    } + +    /// Find a branch and delete it, but before check if "Authorization" +    /// matches with SECRET_KEY +    pub async fn delete(pool: Pool, id: &Uuid) -> Result<Branch, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); +        let statement = client +            .prepare( +                " +                DELETE FROM branch +                WHERE id=$1 +                RETURNING * +                ", +            ) +            .await?; + +        let branch = client +            .query_opt(&statement, &[&id]) +            .await? +            .map(|row| Branch::from_row_ref(&row).unwrap()); + +        match branch { +            Some(branch) => Ok(branch), +            None => Err(AppError { +                error_type: AppErrorType::NotFoundError, +                cause: None, +                message: Some("Branch not found".to_string()), +            }), +        } +    } + +    /// Create a new branch +    pub async fn create( +        pool: Pool, +        data: &BranchData, +    ) -> Result<Branch, AppError> { +        let client = get_client(pool.clone()).await.unwrap(); + +        let statement = client +            .prepare( +                "INSERT INTO repository(id, name, repository_id, head) +                VALUES($1, $2, $3, $4) +                RETURNING *", +            ) +            .await?; + +        // Create a new UUID v4 +        let uuid = Uuid::new_v4(); + +        let branch = client +            .query_opt( +                &statement, +                &[&uuid, &data.name, &data.repository_id, &data.head], +            ) +            .await? +            .map(|row| Branch::from_row_ref(&row).unwrap()); + +        match branch { +            Some(branch) => Ok(branch), +            None => Err(AppError { +                message: Some("Error creating a new branch".to_string()), +                cause: Some("Unknown error".to_string()), +                error_type: AppErrorType::DbError, +            }), +        } +    } +} diff --git a/src/branch/routes.rs b/src/branch/routes.rs new file mode 100644 index 0000000..e46d3ad --- /dev/null +++ b/src/branch/routes.rs @@ -0,0 +1,110 @@ +use crate::branch::models::Branch; +use crate::config::AppState; +use crate::errors::{AppError, AppErrorResponse, AppErrorType}; +use crate::helpers::uuid_from_string; + +use actix_web::http::header; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use slog::info; +use std::env; +use uuid::Uuid; + +/// Endpoint used for getting all commits +async fn index(state: web::Data<AppState>) -> impl Responder { +    info!(state.log, "GET /branch/"); +    let result = Branch::find_all(state.pool.clone()).await; + +    match result { +        Ok(branches) => HttpResponse::Ok().json(branches), +        _ => HttpResponse::BadRequest().json(AppErrorResponse { +            detail: "Error trying to read all branches from database" +                .to_string(), +        }), +    } +} + +// Endpoint used for getting branches of a repository +async fn get_repo_branch( +    state: web::Data<AppState>, +    repo: web::Path<(String,)>, +) -> impl Responder { +    let uuid: Uuid = uuid_from_string(&repo.0); +    info!(state.log, "GET /branch/repo/{}/", &uuid); + +    let result = Branch::find_by_repo(state.pool.clone(), &uuid).await; + +    result +        .map(|branches| HttpResponse::Ok().json(branches)) +        .map_err(|e| e) +} + +/// Endpoint used for retrieve a repository that matches with an `id`. +/// It is a String, casted in an Uuid format. +async fn get_branch( +    state: web::Data<AppState>, +    id: web::Path<(String,)>, +) -> impl Responder { +    let uuid: Uuid = uuid_from_string(&id.0); + +    let result = Branch::find(state.pool.clone(), &uuid).await; +    info!(state.log, "GET /branch/{}/", id.0); + +    // `map_err` is also used when repo is not found +    result +        .map(|repo| HttpResponse::Ok().json(repo)) +        .map_err(|e| e) +} + +/// Endpoint used for delete branch. +/// It uses a SECRET_KEY used like an API key +async fn delete_branch( +    req: HttpRequest, +    state: web::Data<AppState>, +    id: web::Path<(String,)>, +) -> impl Responder { +    let uuid: Uuid = uuid_from_string(&id.0); +    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 /branch/{}/ 401", id.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 /branch/{}/ 400", id.0); +            return Ok(HttpResponse::BadRequest().body("")); +        } +    }; + +    let result = Branch::delete(state.pool.clone(), &uuid).await; +    info!(state.log, "DELETE /branch/{}/", id.0); + +    result +        .map(|_| HttpResponse::NoContent().body("")) +        .map_err(|e| e) +} + +/// Routes for branches +pub fn config(cfg: &mut web::ServiceConfig) { +    cfg.service( +        web::scope("/branch") +            .service(web::resource("{_:/?}").route(web::get().to(index))) +            .service( +                web::resource("/repo/{repo_id}{_:/?}") +                    .route(web::get().to(get_repo_branch)), +            ) +            .service( +                web::resource("/{id}{_:/?}") +                    .route(web::get().to(get_branch)) +                    .route(web::delete().to(delete_branch)), +            ), +    ); +} | 
