From 44a35e651741afb6c417da47d636e4380cdd225f Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Sat, 13 Mar 2021 10:17:02 +0100 Subject: feat: add get all repos from db --- src/db.rs | 8 +++++ src/errors.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 16 +++++----- src/repository/mod.rs | 2 ++ src/repository/models.rs | 36 ++++++++++++++++++++++ src/repository/routes.rs | 23 ++++++++++++++ 6 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/db.rs create mode 100644 src/errors.rs create mode 100644 src/repository/mod.rs create mode 100644 src/repository/models.rs create mode 100644 src/repository/routes.rs (limited to 'src') diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..f547ab2 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,8 @@ +use crate::errors::AppError; +use deadpool_postgres::{Client, Pool, PoolError}; + +pub async fn get_client(pool: Pool) -> Result { + pool.get() + .await + .map_err(|err: PoolError| AppError::from(err)) +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..ee3ca71 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,80 @@ +use actix_web::{error::ResponseError, http::StatusCode, HttpResponse}; +use deadpool_postgres::PoolError; +use serde::Serialize; +use std::fmt; +use tokio_postgres::error::Error; + +#[derive(Debug)] +pub enum AppErrorType { + DbError, + NotFoundError, +} + +#[derive(Debug)] +pub struct AppError { + pub message: Option, + pub cause: Option, + pub error_type: AppErrorType, +} + +impl AppError { + pub fn message(&self) -> String { + match &*self { + AppError { + message: Some(message), + .. + } => message.clone(), + AppError { + message: None, + error_type: AppErrorType::NotFoundError, + .. + } => "The requested item was not found".to_string(), + _ => "An unexpected error has occurred".to_string(), + } + } +} + +impl From for AppError { + fn from(error: PoolError) -> AppError { + AppError { + message: None, + cause: Some(error.to_string()), + error_type: AppErrorType::DbError, + } + } +} + +impl From for AppError { + fn from(error: Error) -> AppError { + AppError { + message: None, + cause: Some(error.to_string()), + error_type: AppErrorType::DbError, + } + } +} + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{:?}", self) + } +} + +#[derive(Serialize)] +pub struct AppErrorResponse { + pub error: String, +} + +impl ResponseError for AppError { + fn status_code(&self) -> StatusCode { + match self.error_type { + AppErrorType::DbError => StatusCode::INTERNAL_SERVER_ERROR, + AppErrorType::NotFoundError => StatusCode::NOT_FOUND, + } + } + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()).json(AppErrorResponse { + error: self.message(), + }) + } +} diff --git a/src/main.rs b/src/main.rs index 0b3d67f..176dae1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,15 @@ mod config; +mod db; +mod errors; -use actix_web::{middleware, web, App, HttpServer}; +mod repository; + +use actix_web::{middleware, App, HttpServer}; use dotenv::dotenv; use slog::info; use tokio_postgres::NoTls; -use crate::config::{Config, AppState}; - -async fn index(state: web::Data) -> &'static str { - info!(state.log, "GET `/` page"); - - "Hello from Rust!" -} +use crate::config::{AppState, Config}; #[actix_rt::main] async fn main() -> std::io::Result<()> { @@ -35,7 +33,7 @@ async fn main() -> std::io::Result<()> { log: log.clone(), }) .wrap(middleware::Logger::default()) - .route("/", web::get().to(index)) + .configure(repository::routes::config) }) .bind(format!("{}:{}", config.server.host, config.server.port))? .run() diff --git a/src/repository/mod.rs b/src/repository/mod.rs new file mode 100644 index 0000000..a0e1883 --- /dev/null +++ b/src/repository/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod routes; diff --git a/src/repository/models.rs b/src/repository/models.rs new file mode 100644 index 0000000..ec5559b --- /dev/null +++ b/src/repository/models.rs @@ -0,0 +1,36 @@ +use crate::db::get_client; +use crate::errors::AppError; +use chrono::NaiveDateTime; +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 = "repository")] +pub struct Repository { + pub id: Uuid, + pub url: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub uploader_ip: String, +} + +impl Repository { + pub async fn find_all(pool: Pool) -> Result, AppError> { + let client = get_client(pool.clone()).await.unwrap(); + let statement = client + .prepare("SELECT * FROM repository ORDER BY updated_at DESC") + .await?; + + let repos = client + .query(&statement, &[]) + .await? + .iter() + .map(|row| Repository::from_row_ref(row).unwrap()) + .collect::>(); + + Ok(repos) + } +} diff --git a/src/repository/routes.rs b/src/repository/routes.rs new file mode 100644 index 0000000..ebfff8e --- /dev/null +++ b/src/repository/routes.rs @@ -0,0 +1,23 @@ +use crate::config::AppState; +use crate::repository::models::Repository; +use actix_web::{web, HttpResponse, Responder}; +use slog::info; + +async fn index(state: web::Data) -> impl Responder { + let result = Repository::find_all(state.pool.clone()).await; + match result { + Ok(repos) => { + info!(state.log, "GET /repo/ 200"); + HttpResponse::Ok().json(repos) + } + _ => { + info!(state.log, "GET /repo/ 500"); + HttpResponse::BadRequest() + .body("Error trying to read all repositories from database") + } + } +} + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("/repo/").route(web::get().to(index))); +} -- cgit v1.2.3-18-g5258