Skip to content

Commit

Permalink
refactor(todos): better examples and ussage of core module
Browse files Browse the repository at this point in the history
  • Loading branch information
ncrmro committed Jun 17, 2020
1 parent 92b2ea3 commit 38d5b06
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 100 deletions.
27 changes: 27 additions & 0 deletions src/todo/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use async_trait::async_trait;

use super::model::{Todo as TodoModel, TodoRequest};
use crate::core::db;
use crate::core::db::model::DatabaseModel;
use crate::core::factory::ModelFactory;
use fake::{faker::internet, Fake};

pub struct Todo;

#[async_trait]
impl ModelFactory<TodoModel, TodoRequest> for Todo {
fn build() -> TodoRequest {
TodoRequest {
description: "".to_string(),
done: false,
}
}

async fn create(
request: Option<TodoRequest>,
conn: db::PgPool,
) -> Result<TodoModel, db::Error> {
let obj_build = request.unwrap_or_else(Self::build);
TodoModel::create(obj_build, &conn).await
}
}
3 changes: 2 additions & 1 deletion src/todo/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod model;
pub mod factory;
pub mod model;
mod routes;

pub use model::*;
Expand Down
123 changes: 28 additions & 95 deletions src/todo/model.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,56 @@
use actix_web::{Error, HttpRequest, HttpResponse, Responder};
use futures::future::{ready, Ready};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool};
// use sqlx::postgres::PgRow;
use async_trait::async_trait;

use anyhow::Result;
use paperclip::actix::Apiv2Schema;

use crate::core::db;
use crate::core::db::model::DatabaseModel;
use crate::core::db::FromRow;
use crate::core::http::Apiv2Schema;

// this struct will use to receive user input
#[derive(Serialize, Deserialize, Apiv2Schema)]
#[derive(Serialize, Deserialize, Apiv2Schema, Clone)]
pub struct TodoRequest {
pub description: String,
pub done: bool,
}

// this struct will be used to represent database record
#[derive(Serialize, FromRow, Apiv2Schema)]
#[derive(Serialize, FromRow, Apiv2Schema, Clone)]
pub struct Todo {
pub id: i32,
pub description: String,
pub done: bool,
}

// implementation of Actix Responder for Todo struct so we can return Todo from action handler
impl Responder for Todo {
type Error = Error;
type Future = Ready<Result<HttpResponse, Error>>;

fn respond_to(self, _req: &HttpRequest) -> Self::Future {
let body = serde_json::to_string(&self).unwrap();
// create response and set content type
ready(Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body)))
}
}

// Implementation for Todo struct, functions for read/write/update and delete todo from database
impl Todo {
// pub async fn find_all(pool: &PgPool) -> Result<Vec<Todo>> {
// let mut todos = vec![];
// let recs = sqlx::query!(
// r#"
// SELECT id, description, done
// FROM todos
// ORDER BY id
// "#
// )
// .fetch_all(pool)
// .await?;
//
// for rec in recs {
// todos.push(Todo {
// id: rec.id,
// description: rec.description,
// done: rec.done
// });
// }
//
// Ok(todos)
// }
#[async_trait]
impl DatabaseModel<Todo, TodoRequest> for Todo {
async fn create(obj: TodoRequest, conn: &db::PgPool) -> Result<Self, db::Error> {
let rec = sqlx::query_as!(
Todo,
"INSERT INTO todos (description, done) VALUES ($1, $2) RETURNING *",
obj.description,
false
)
.fetch_one(conn)
.await;

match rec {
Ok(part) => Ok(part),
Err(e) => Err(e),
}
}

pub async fn find_by_id(id: i32, pool: &PgPool) -> Result<Todo> {
async fn get(id: i32, db_conn: &db::PgPool) -> Result<Todo, db::Error> {
let rec = sqlx::query!(
r#"
SELECT * FROM todos WHERE id = $1
"#,
id
)
.fetch_one(&*pool)
.fetch_one(&*db_conn)
.await?;

Ok(Todo {
Expand All @@ -76,54 +59,4 @@ impl Todo {
done: rec.done,
})
}
//
// pub async fn create(todo: TodoRequest, pool: &PgPool) -> Result<Todo> {
// let mut tx = pool.begin().await?;
// let todo = sqlx::query("INSERT INTO todos (description, done) VALUES ($1, $2) RETURNING id, description, done")
// .bind(&todo.description)
// .bind(todo.done)
// .map(|row: PgRow| {
// Todo {
// id: row.get(0),
// description: row.get(1),
// done: row.get(2)
// }
// })
// .fetch_one(&mut tx)
// .await?;
//
// tx.commit().await?;
// Ok(todo)
// }
//
// pub async fn update(id: i32, todo: TodoRequest, pool: &PgPool) -> Result<Todo> {
// let mut tx = pool.begin().await.unwrap();
// let todo = sqlx::query("UPDATE todos SET description = $1, done = $2 WHERE id = $3 RETURNING id, description, done")
// .bind(&todo.description)
// .bind(todo.done)
// .bind(id)
// .map(|row: PgRow| {
// Todo {
// id: row.get(0),
// description: row.get(1),
// done: row.get(2)
// }
// })
// .fetch_one(&mut tx)
// .await?;
//
// tx.commit().await.unwrap();
// Ok(todo)
// }
//
// pub async fn delete(id: i32, pool: &PgPool) -> Result<u64> {
// let mut tx = pool.begin().await?;
// let deleted = sqlx::query("DELETE FROM todos WHERE id = $1")
// .bind(id)
// .execute(&mut tx)
// .await?;
//
// tx.commit().await?;
// Ok(deleted)
// }
}
28 changes: 24 additions & 4 deletions src/todo/routes.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
use futures::future::{ready, Ready};

use crate::core::db::PgPool;
use crate::todo::Todo;
use paperclip::actix::web::{self, get, Data, Json};
use sqlx::PgPool;

use crate::core::db::model::DatabaseModel;
use crate::core::http::errors::Error;
use crate::core::http::HttpRequest;
use crate::core::http::HttpResponse;
use crate::core::http::Responder;

impl Responder for Todo {
type Error = Error;
type Future = Ready<Result<HttpResponse, Error>>;

fn respond_to(self, _req: &HttpRequest) -> Self::Future {
let body = serde_json::to_string(&self).unwrap();
// create response and set content type
ready(Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body)))
}
}

async fn find(id: web::Path<i32>, db_pool: Data<PgPool>) -> Result<Json<Todo>, ()> {
let result = Todo::find_by_id(id.into_inner(), db_pool.get_ref()).await;
let result = Todo::get(id.into_inner(), db_pool.get_ref()).await;
match result {
Ok(todo) => Ok(Json(todo)),
_ => Err(()),
Expand All @@ -12,6 +33,5 @@ async fn find(id: web::Path<i32>, db_pool: Data<PgPool>) -> Result<Json<Todo>, (

// function that will be called on new Application to configure routes for this module
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.route("/todo", get().to(find))
.route("/todo1", get().to(find));
cfg.route("/todo", get().to(find));
}
1 change: 1 addition & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extern crate planet_express as src;

mod common;
mod todos;
mod users;

use actix_web::test;
Expand Down
50 changes: 50 additions & 0 deletions tests/todos/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use super::super::common;
use actix_web::http;
use actix_web::test;
use src::user::model::User;
use src::user::routes::AuthenticationResponse;

#[actix_rt::test]
async fn test_auth_viewer_create() {
let (mut srv, db_conn, test_name) = common::setup().await;
let obj = planet_express::user::UserFactory::build();
let req = test::TestRequest::post()
.uri("/v1/viewer/create")
.set_json(&obj)
.to_request();
let res: AuthenticationResponse = test::read_response_json(&mut srv, req).await;
assert_eq!(res.user.email, obj.email);

// Test user exists error
let req = test::TestRequest::post()
.uri("/v1/viewer/create")
.set_json(&obj)
.to_request();

// Test user already exists
let mut res = test::call_service(&mut srv, req).await;

assert_eq!(res.status(), http::StatusCode::CONFLICT);
common::teardown(db_conn, test_name).await;
}

#[actix_rt::test]
async fn test_auth_viewer_authenticate() {
let (mut srv, db_conn, test_name) = common::setup().await;
let obj = planet_express::user::UserFactory::create(db_conn.clone()).await;
let req = test::TestRequest::post()
.uri("/v1/viewer/authenticate")
.set_json(&obj)
.to_request();

let res: AuthenticationResponse = test::read_response_json(&mut srv, req).await;
assert_eq!(res.user.email, obj.email.clone());

let req = test::TestRequest::get()
.uri("/v1/viewer")
.header("Authorization", format!("Bearer {}", res.token))
.to_request();
let res: User = test::read_response_json(&mut srv, req).await;
assert_eq!(res.email, obj.email.clone());
common::teardown(db_conn, test_name).await;
}
1 change: 1 addition & 0 deletions tests/todos/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod model;
26 changes: 26 additions & 0 deletions tests/todos/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use super::super::common;
use src::core::db::model::DatabaseModel;
use src::core::factory::ModelFactory;
use src::todo::factory;
use src::todo::model;

#[actix_rt::test]
async fn test_model_create() {
let (_srv, db_conn, test_name) = common::setup().await;
let obj = factory::Todo::build();

let record = model::Todo::create(obj.clone(), &db_conn).await.unwrap();

assert_eq!(obj.description, record.description);
common::teardown(db_conn, test_name).await;
}

#[actix_rt::test]
async fn test_model_get_by_id() {
let (_srv, db_conn, test_name) = common::setup().await;
let obj = factory::Todo::create(None, db_conn.clone()).await.unwrap();
let record = model::Todo::get(obj.id, &db_conn).await.unwrap();

assert_eq!(obj.description, record.description);
common::teardown(db_conn, test_name).await;
}

0 comments on commit 38d5b06

Please sign in to comment.