Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
resolver = "2"
members = [
"application",
"database_async",
"database_sync",
"errors",
"extractors",
"getting_started",
Expand All @@ -17,6 +15,10 @@ members = [
"testing",
"url_dispatch",
"websockets",
"diesel",
"diesel_async",
"seaorm",
"sqlx",
]

[workspace.package]
Expand All @@ -25,12 +27,15 @@ edition = "2021"
rust-version = "1.84"

[workspace.dependencies]
actix-web = { version = "*", features = ["openssl", "rustls"] }
actix-web = { version = "4.9", features = ["openssl", "rustls"] }
actix-files = "0.6"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
futures = "*"
env_logger = "*"
log = "*"
futures-util = "*"
uuid = { version = "1.11.0", features = ["v7", "serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1.11", features = ["v7", "serde"] }
futures = "0.3"
futures-util = "0.3"
dotenvy = "0.15"
env_logger = "0.11"
log = "0.4"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

2 changes: 1 addition & 1 deletion diesel/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# diesel
# Diesel

Basic integration of [Diesel](https://diesel.rs) using SQLite for Actix Web.

Expand Down
3 changes: 2 additions & 1 deletion diesel_async/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ edition = "2021"
actix-web.workspace = true
diesel = { version = "2", default-features = false, features = ["uuid"] }
diesel-async = { version = "0.5", features = ["postgres", "bb8", "async-connection-wrapper"] }
tracing-subscriber.workspace = true
serde.workspace = true
uuid.workspace = true
dotenvy = "0.15"
dotenvy.workspace = true
env_logger.workspace = true
log.workspace = true
20 changes: 10 additions & 10 deletions diesel_async/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# diesel
# Diesel-async

Basic integration of [Diesel-async](https://github.com/weiznich/diesel_async) using PostgreSQL for Actix Web.
A basic example of interaction between Actix Web and [Diesel-async](https://github.com/weiznich/diesel_async) using PostgreSQL.

## Usage

### Install PostgreSQL

```sh
# on any OS
docker run -d --restart unless-stopped --name postgresql -e POSTGRES_USER=test-user -e POSTGRES_PASSWORD=password -p 5432:5432 -v postgres_data:/var/lib/postgresql/data postgres:alpine
docker run -d --restart unless-stopped --name postgresql -e POSTGRES_USER=adiesel-user -e POSTGRES_PASSWORD=password -p 5432:5432 -v postgres_data:/var/lib/postgresql/data postgres:alpine
```

### Initialize PostgreSQL Database
Expand All @@ -17,7 +17,7 @@ docker run -d --restart unless-stopped --name postgresql -e POSTGRES_USER=test-u
cd databases/diesel-async
cargo install diesel_cli --no-default-features --features postgres

echo DATABASE_URL=postgres://test-user:password@localhost:5432/test_db > .env
echo DATABASE_URL=postgres://adiesel-user:password@localhost:5432/adiesel-db > .env
diesel setup
diesel migration run
```
Expand All @@ -42,18 +42,18 @@ cargo run

Inserts a new item into the PostgreSQL DB.

Provide a JSON payload with a name. Eg:
Provide a JSON payload with a title. Eg:

```json
{ "name": "thingamajig" }
{ "title": "thingamajig" }
```

On success, a response like the following is returned:

```json
{
"id": "01948982-67d0-7a55-b4b1-8b8b962d8c6b",
"name": "thingamajig"
"title": "thingamajig"
}
```

Expand All @@ -63,13 +63,13 @@ On success, a response like the following is returned:
Using [HTTPie]:

```sh
http POST localhost:8080/items name=thingamajig
http POST localhost:8080/items title=thingamajig
```

Using cURL:

```sh
curl -S -X POST --header "Content-Type: application/json" --data '{"name":"thingamajig"}' http://localhost:8080/items
curl -S -X POST --header "Content-Type: application/json" --data '{"title":"thingamajig"}' http://localhost:8080/items
```

</details>
Expand Down Expand Up @@ -98,7 +98,7 @@ curl -S http://localhost:8080/items/9e46baba-a001-4bb3-b4cf-4b3e5bab5e97
### Explore The PostgreSQL DB

```sh
docker exec -i postgresql psql -U test-user -d test_db -c "select * from public.items"
docker exec -i postgresql psql -U test-user -d adiesel-db -c "select * from public.items"
```

## Using Other Databases
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS items
(
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name VARCHAR NOT NULL
title VARCHAR NOT NULL
);
4 changes: 2 additions & 2 deletions diesel_async/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub async fn find_item_by_uid(
/// Run query using Diesel to insert a new database row and return the result.
pub async fn insert_new_item(
conn: &mut AsyncPgConnection,
nm: &str, // prevent collision with `name` column imported inside the function
nm: &str, // prevent collision with `title` column imported inside the function
) -> Result<models::Item, DbError> {
// It is common when using Diesel with Actix Web to import schema-related
// modules inside a function's scope (rather than the normal module's scope)
Expand All @@ -38,7 +38,7 @@ pub async fn insert_new_item(

let new_item = models::Item {
id: Uuid::new_v7(Timestamp::now(NoContext)),
name: nm.to_owned(),
title: nm.to_owned(),
};

let item = diesel::insert_into(items)
Expand Down
58 changes: 58 additions & 0 deletions diesel_async/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::{actions, models, DbPool};
use actix_web::{error, get, post, web, HttpResponse, Responder};
use uuid::Uuid;

/// Finds item by UID.
///
/// Extracts:
/// - the database pool handle from application data
/// - an item UID from the request path
#[get("/items/{item_id}")]
async fn get_item(
pool: web::Data<DbPool>,
item_uid: web::Path<Uuid>,
) -> actix_web::Result<impl Responder> {
let item_uid = item_uid.into_inner();

let mut conn = pool
.get()
.await
.expect("Couldn't get db connection from the pool");

let item = actions::find_item_by_uid(&mut conn, item_uid)
.await
// map diesel query errors to a 500 error response
.map_err(error::ErrorInternalServerError)?;

Ok(match item {
// item was found; return 200 response with JSON formatted item object
Some(item) => HttpResponse::Ok().json(item),

// item was not found; return 404 response with error message
None => HttpResponse::NotFound().body(format!("No item found with UID: {item_uid}")),
})
}

/// Creates new item.
///
/// Extracts:
/// - the database pool handle from application data
/// - a JSON form containing new item info from the request body
#[post("/items")]
async fn add_item(
pool: web::Data<DbPool>,
form: web::Json<models::NewItem>,
) -> actix_web::Result<impl Responder> {
let mut conn = pool
.get()
.await
.expect("Couldn't get db connection from the pool");

let item = actions::insert_new_item(&mut conn, &form.title)
.await
// map diesel query errors to a 500 error response
.map_err(error::ErrorInternalServerError)?;

// item was added successfully; return 201 response with new item info
Ok(HttpResponse::Created().json(item))
}
77 changes: 11 additions & 66 deletions diesel_async/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,21 @@
#[macro_use]
extern crate diesel;

use actix_web::{error, get, middleware, post, web, App, HttpResponse, HttpServer, Responder};
use actix_web::{middleware, web, App, HttpServer};
use diesel_async::{
pooled_connection::{bb8::Pool, AsyncDieselConnectionManager},
AsyncPgConnection,
};
use dotenvy::dotenv;
use std::{env, io};
use uuid::Uuid;

mod actions;
mod api;
mod models;
mod schema;

type DbPool = Pool<AsyncPgConnection>;

/// Finds item by UID.
///
/// Extracts:
/// - the database pool handle from application data
/// - an item UID from the request path
#[get("/items/{item_id}")]
async fn get_item(
pool: web::Data<DbPool>,
item_uid: web::Path<Uuid>,
) -> actix_web::Result<impl Responder> {
let item_uid = item_uid.into_inner();

let mut conn = pool
.get()
.await
.expect("Couldn't get db connection from the pool");

let item = actions::find_item_by_uid(&mut conn, item_uid)
.await
// map diesel query errors to a 500 error response
.map_err(error::ErrorInternalServerError)?;

Ok(match item {
// item was found; return 200 response with JSON formatted item object
Some(item) => HttpResponse::Ok().json(item),

// item was not found; return 404 response with error message
None => HttpResponse::NotFound().body(format!("No item found with UID: {item_uid}")),
})
}

/// Creates new item.
///
/// Extracts:
/// - the database pool handle from application data
/// - a JSON form containing new item info from the request body
#[post("/items")]
async fn add_item(
pool: web::Data<DbPool>,
form: web::Json<models::NewItem>,
) -> actix_web::Result<impl Responder> {
let mut conn = pool
.get()
.await
.expect("Couldn't get db connection from the pool");

let item = actions::insert_new_item(&mut conn, &form.name)
.await
// map diesel query errors to a 500 error response
.map_err(error::ErrorInternalServerError)?;

// item was added successfully; return 201 response with new item info
Ok(HttpResponse::Created().json(item))
}

#[actix_web::main]
async fn main() -> io::Result<()> {
dotenv().ok();
Expand All @@ -88,10 +33,10 @@ async fn main() -> io::Result<()> {
// add request logger middleware
.wrap(middleware::Logger::default())
// add route handlers
.service(add_item)
.service(get_item)
.service(api::add_item)
.service(api::get_item)
})
.bind(("127.0.0.1", 8080))?
.bind(("127.0.0.1", 5000))?
.run()
.await
}
Expand All @@ -108,11 +53,11 @@ async fn initialize_db_pool() -> DbPool {

#[cfg(test)]
mod tests {
use super::*;
use actix_web::{http::StatusCode, test};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;

use super::*;
use uuid::Uuid;

#[actix_web::test]
async fn item_routes() {
Expand All @@ -125,8 +70,8 @@ mod tests {
App::new()
.app_data(web::Data::new(pool.clone()))
.wrap(middleware::Logger::default())
.service(get_item)
.service(add_item),
.service(api::get_item)
.service(api::add_item),
)
.await;

Expand Down Expand Up @@ -158,14 +103,14 @@ mod tests {
.set_json(models::NewItem::new("Test item"))
.to_request();
let res: models::Item = test::call_and_read_body_json(&app, req).await;
assert_eq!(res.name, "Test item");
assert_eq!(res.title, "Test item");

// get an item
let req = test::TestRequest::get()
.uri(&format!("/items/{}", res.id))
.to_request();
let res: models::Item = test::call_and_read_body_json(&app, req).await;
assert_eq!(res.name, "Test item");
assert_eq!(res.title, "Test item");

// delete new item from table
use crate::schema::items::dsl::*;
Expand Down
12 changes: 7 additions & 5 deletions diesel_async/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ use uuid::Uuid;
#[diesel(table_name = items)]
pub struct Item {
pub id: Uuid,
pub name: String,
pub title: String,
}

/// New item details.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NewItem {
pub name: String,
pub title: String,
}

impl NewItem {
/// Constructs new item details from name.
/// Constructs new item details from title.
#[cfg(test)] // only needed in tests
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
}
}
}
2 changes: 1 addition & 1 deletion diesel_async/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
diesel::table! {
items (id) {
id -> Uuid,
name -> Varchar,
title -> Varchar,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]

[migrations_directory]
dir = "/home/alex/CLionProjects/actix-with-async-diesel/migrations"
dir = "/home/alex/CLionProjects/actix-practice/diesel_async/migrations"
Loading