Skip to content

Commit 38d5b06

Browse files
committed
refactor(todos): better examples and ussage of core module
1 parent 92b2ea3 commit 38d5b06

File tree

8 files changed

+159
-100
lines changed

8 files changed

+159
-100
lines changed

src/todo/factory.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use async_trait::async_trait;
2+
3+
use super::model::{Todo as TodoModel, TodoRequest};
4+
use crate::core::db;
5+
use crate::core::db::model::DatabaseModel;
6+
use crate::core::factory::ModelFactory;
7+
use fake::{faker::internet, Fake};
8+
9+
pub struct Todo;
10+
11+
#[async_trait]
12+
impl ModelFactory<TodoModel, TodoRequest> for Todo {
13+
fn build() -> TodoRequest {
14+
TodoRequest {
15+
description: "".to_string(),
16+
done: false,
17+
}
18+
}
19+
20+
async fn create(
21+
request: Option<TodoRequest>,
22+
conn: db::PgPool,
23+
) -> Result<TodoModel, db::Error> {
24+
let obj_build = request.unwrap_or_else(Self::build);
25+
TodoModel::create(obj_build, &conn).await
26+
}
27+
}

src/todo/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
mod model;
1+
pub mod factory;
2+
pub mod model;
23
mod routes;
34

45
pub use model::*;

src/todo/model.rs

Lines changed: 28 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,56 @@
1-
use actix_web::{Error, HttpRequest, HttpResponse, Responder};
2-
use futures::future::{ready, Ready};
3-
use serde::{Deserialize, Serialize};
4-
use sqlx::{FromRow, PgPool};
5-
// use sqlx::postgres::PgRow;
1+
use async_trait::async_trait;
2+
63
use anyhow::Result;
7-
use paperclip::actix::Apiv2Schema;
4+
5+
use crate::core::db;
6+
use crate::core::db::model::DatabaseModel;
7+
use crate::core::db::FromRow;
8+
use crate::core::http::Apiv2Schema;
89

910
// this struct will use to receive user input
10-
#[derive(Serialize, Deserialize, Apiv2Schema)]
11+
#[derive(Serialize, Deserialize, Apiv2Schema, Clone)]
1112
pub struct TodoRequest {
1213
pub description: String,
1314
pub done: bool,
1415
}
1516

1617
// this struct will be used to represent database record
17-
#[derive(Serialize, FromRow, Apiv2Schema)]
18+
#[derive(Serialize, FromRow, Apiv2Schema, Clone)]
1819
pub struct Todo {
1920
pub id: i32,
2021
pub description: String,
2122
pub done: bool,
2223
}
2324

2425
// implementation of Actix Responder for Todo struct so we can return Todo from action handler
25-
impl Responder for Todo {
26-
type Error = Error;
27-
type Future = Ready<Result<HttpResponse, Error>>;
28-
29-
fn respond_to(self, _req: &HttpRequest) -> Self::Future {
30-
let body = serde_json::to_string(&self).unwrap();
31-
// create response and set content type
32-
ready(Ok(HttpResponse::Ok()
33-
.content_type("application/json")
34-
.body(body)))
35-
}
36-
}
3726

3827
// Implementation for Todo struct, functions for read/write/update and delete todo from database
39-
impl Todo {
40-
// pub async fn find_all(pool: &PgPool) -> Result<Vec<Todo>> {
41-
// let mut todos = vec![];
42-
// let recs = sqlx::query!(
43-
// r#"
44-
// SELECT id, description, done
45-
// FROM todos
46-
// ORDER BY id
47-
// "#
48-
// )
49-
// .fetch_all(pool)
50-
// .await?;
51-
//
52-
// for rec in recs {
53-
// todos.push(Todo {
54-
// id: rec.id,
55-
// description: rec.description,
56-
// done: rec.done
57-
// });
58-
// }
59-
//
60-
// Ok(todos)
61-
// }
28+
#[async_trait]
29+
impl DatabaseModel<Todo, TodoRequest> for Todo {
30+
async fn create(obj: TodoRequest, conn: &db::PgPool) -> Result<Self, db::Error> {
31+
let rec = sqlx::query_as!(
32+
Todo,
33+
"INSERT INTO todos (description, done) VALUES ($1, $2) RETURNING *",
34+
obj.description,
35+
false
36+
)
37+
.fetch_one(conn)
38+
.await;
39+
40+
match rec {
41+
Ok(part) => Ok(part),
42+
Err(e) => Err(e),
43+
}
44+
}
6245

63-
pub async fn find_by_id(id: i32, pool: &PgPool) -> Result<Todo> {
46+
async fn get(id: i32, db_conn: &db::PgPool) -> Result<Todo, db::Error> {
6447
let rec = sqlx::query!(
6548
r#"
6649
SELECT * FROM todos WHERE id = $1
6750
"#,
6851
id
6952
)
70-
.fetch_one(&*pool)
53+
.fetch_one(&*db_conn)
7154
.await?;
7255

7356
Ok(Todo {
@@ -76,54 +59,4 @@ impl Todo {
7659
done: rec.done,
7760
})
7861
}
79-
//
80-
// pub async fn create(todo: TodoRequest, pool: &PgPool) -> Result<Todo> {
81-
// let mut tx = pool.begin().await?;
82-
// let todo = sqlx::query("INSERT INTO todos (description, done) VALUES ($1, $2) RETURNING id, description, done")
83-
// .bind(&todo.description)
84-
// .bind(todo.done)
85-
// .map(|row: PgRow| {
86-
// Todo {
87-
// id: row.get(0),
88-
// description: row.get(1),
89-
// done: row.get(2)
90-
// }
91-
// })
92-
// .fetch_one(&mut tx)
93-
// .await?;
94-
//
95-
// tx.commit().await?;
96-
// Ok(todo)
97-
// }
98-
//
99-
// pub async fn update(id: i32, todo: TodoRequest, pool: &PgPool) -> Result<Todo> {
100-
// let mut tx = pool.begin().await.unwrap();
101-
// let todo = sqlx::query("UPDATE todos SET description = $1, done = $2 WHERE id = $3 RETURNING id, description, done")
102-
// .bind(&todo.description)
103-
// .bind(todo.done)
104-
// .bind(id)
105-
// .map(|row: PgRow| {
106-
// Todo {
107-
// id: row.get(0),
108-
// description: row.get(1),
109-
// done: row.get(2)
110-
// }
111-
// })
112-
// .fetch_one(&mut tx)
113-
// .await?;
114-
//
115-
// tx.commit().await.unwrap();
116-
// Ok(todo)
117-
// }
118-
//
119-
// pub async fn delete(id: i32, pool: &PgPool) -> Result<u64> {
120-
// let mut tx = pool.begin().await?;
121-
// let deleted = sqlx::query("DELETE FROM todos WHERE id = $1")
122-
// .bind(id)
123-
// .execute(&mut tx)
124-
// .await?;
125-
//
126-
// tx.commit().await?;
127-
// Ok(deleted)
128-
// }
12962
}

src/todo/routes.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
1+
use futures::future::{ready, Ready};
2+
3+
use crate::core::db::PgPool;
14
use crate::todo::Todo;
25
use paperclip::actix::web::{self, get, Data, Json};
3-
use sqlx::PgPool;
6+
7+
use crate::core::db::model::DatabaseModel;
8+
use crate::core::http::errors::Error;
9+
use crate::core::http::HttpRequest;
10+
use crate::core::http::HttpResponse;
11+
use crate::core::http::Responder;
12+
13+
impl Responder for Todo {
14+
type Error = Error;
15+
type Future = Ready<Result<HttpResponse, Error>>;
16+
17+
fn respond_to(self, _req: &HttpRequest) -> Self::Future {
18+
let body = serde_json::to_string(&self).unwrap();
19+
// create response and set content type
20+
ready(Ok(HttpResponse::Ok()
21+
.content_type("application/json")
22+
.body(body)))
23+
}
24+
}
425

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

1334
// function that will be called on new Application to configure routes for this module
1435
pub fn init(cfg: &mut web::ServiceConfig) {
15-
cfg.route("/todo", get().to(find))
16-
.route("/todo1", get().to(find));
36+
cfg.route("/todo", get().to(find));
1737
}

tests/integration_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
extern crate planet_express as src;
22

33
mod common;
4+
mod todos;
45
mod users;
56

67
use actix_web::test;

tests/todos/api.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use super::super::common;
2+
use actix_web::http;
3+
use actix_web::test;
4+
use src::user::model::User;
5+
use src::user::routes::AuthenticationResponse;
6+
7+
#[actix_rt::test]
8+
async fn test_auth_viewer_create() {
9+
let (mut srv, db_conn, test_name) = common::setup().await;
10+
let obj = planet_express::user::UserFactory::build();
11+
let req = test::TestRequest::post()
12+
.uri("/v1/viewer/create")
13+
.set_json(&obj)
14+
.to_request();
15+
let res: AuthenticationResponse = test::read_response_json(&mut srv, req).await;
16+
assert_eq!(res.user.email, obj.email);
17+
18+
// Test user exists error
19+
let req = test::TestRequest::post()
20+
.uri("/v1/viewer/create")
21+
.set_json(&obj)
22+
.to_request();
23+
24+
// Test user already exists
25+
let mut res = test::call_service(&mut srv, req).await;
26+
27+
assert_eq!(res.status(), http::StatusCode::CONFLICT);
28+
common::teardown(db_conn, test_name).await;
29+
}
30+
31+
#[actix_rt::test]
32+
async fn test_auth_viewer_authenticate() {
33+
let (mut srv, db_conn, test_name) = common::setup().await;
34+
let obj = planet_express::user::UserFactory::create(db_conn.clone()).await;
35+
let req = test::TestRequest::post()
36+
.uri("/v1/viewer/authenticate")
37+
.set_json(&obj)
38+
.to_request();
39+
40+
let res: AuthenticationResponse = test::read_response_json(&mut srv, req).await;
41+
assert_eq!(res.user.email, obj.email.clone());
42+
43+
let req = test::TestRequest::get()
44+
.uri("/v1/viewer")
45+
.header("Authorization", format!("Bearer {}", res.token))
46+
.to_request();
47+
let res: User = test::read_response_json(&mut srv, req).await;
48+
assert_eq!(res.email, obj.email.clone());
49+
common::teardown(db_conn, test_name).await;
50+
}

tests/todos/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod model;

tests/todos/model.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use super::super::common;
2+
use src::core::db::model::DatabaseModel;
3+
use src::core::factory::ModelFactory;
4+
use src::todo::factory;
5+
use src::todo::model;
6+
7+
#[actix_rt::test]
8+
async fn test_model_create() {
9+
let (_srv, db_conn, test_name) = common::setup().await;
10+
let obj = factory::Todo::build();
11+
12+
let record = model::Todo::create(obj.clone(), &db_conn).await.unwrap();
13+
14+
assert_eq!(obj.description, record.description);
15+
common::teardown(db_conn, test_name).await;
16+
}
17+
18+
#[actix_rt::test]
19+
async fn test_model_get_by_id() {
20+
let (_srv, db_conn, test_name) = common::setup().await;
21+
let obj = factory::Todo::create(None, db_conn.clone()).await.unwrap();
22+
let record = model::Todo::get(obj.id, &db_conn).await.unwrap();
23+
24+
assert_eq!(obj.description, record.description);
25+
common::teardown(db_conn, test_name).await;
26+
}

0 commit comments

Comments
 (0)