diff options
-rw-r--r-- | src/branch/mod.rs | 2 | ||||
-rw-r--r-- | src/branch/models.rs | 148 | ||||
-rw-r--r-- | src/branch/routes.rs | 110 | ||||
-rw-r--r-- | src/main.rs | 2 |
4 files changed, 262 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)), + ), + ); +} diff --git a/src/main.rs b/src/main.rs index b2e0ba3..6f20201 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod db; mod errors; mod helpers; +mod branch; mod commit; mod email; mod repository; @@ -39,6 +40,7 @@ async fn main() -> std::io::Result<()> { .configure(repository::routes::config) .configure(email::routes::config) .configure(commit::routes::config) + .configure(branch::routes::config) }) .bind(format!("{}:{}", config.server.host, config.server.port))? .run() |