From 2aa8b15ce2956a7d1643502323328980ebaf4b2c Mon Sep 17 00:00:00 2001 From: Chris Gubbin Date: Sat, 5 Feb 2022 17:34:43 +0000 Subject: [PATCH] Updated basics/to-do to v4 using sqlx. --- basics/todo/Cargo.toml | 12 ++- basics/todo/README.md | 11 +-- .../down.sql | 6 -- .../up.sql | 36 --------- .../down.sql | 1 - ... => 20220205163207_create_tasks_table.sql} | 0 basics/todo/src/api.rs | 32 ++++---- basics/todo/src/db.rs | 41 ++++------ basics/todo/src/main.rs | 15 ++-- basics/todo/src/model.rs | 81 ++++++++++++------- basics/todo/src/schema.rs | 7 -- 11 files changed, 110 insertions(+), 132 deletions(-) delete mode 100644 basics/todo/migrations/00000000000000_diesel_initial_setup/down.sql delete mode 100644 basics/todo/migrations/00000000000000_diesel_initial_setup/up.sql delete mode 100644 basics/todo/migrations/2018-07-05-163612_create_tasks_table/down.sql rename basics/todo/migrations/{2018-07-05-163612_create_tasks_table/up.sql => 20220205163207_create_tasks_table.sql} (100%) delete mode 100644 basics/todo/src/schema.rs diff --git a/basics/todo/Cargo.toml b/basics/todo/Cargo.toml index cba6a44..7d8a393 100644 --- a/basics/todo/Cargo.toml +++ b/basics/todo/Cargo.toml @@ -4,11 +4,10 @@ version = "1.0.0" edition = "2021" [dependencies] -actix-web = "4.0.0-rc.1" -actix-files = "0.6.0-beta.15" +actix-web = "4.0.0-rc.2" +actix-files = "0.6.0-beta.16" actix-session = "0.5.0-beta.7" -diesel = { version = "1.3.2", features = ["postgres", "r2d2"] } dotenv = "0.15" env_logger = "0.9" futures = "0.3.7" @@ -16,3 +15,10 @@ log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" tera = "1.5" + +[dependencies.sqlx] +features = [ + "runtime-actix-rustls", + "postgres" + ] +version = "0.5.10" \ No newline at end of file diff --git a/basics/todo/README.md b/basics/todo/README.md index 9cabd09..1f31c8d 100644 --- a/basics/todo/README.md +++ b/basics/todo/README.md @@ -19,22 +19,23 @@ cd basics/todo ## Set up the database -Install the [diesel](http://diesel.rs) command-line tool including the `postgres` feature: +Install the [sqlx](https://github.com/launchbadge/sqlx) command-line tool including the `postgres` feature: ```bash -cargo install diesel_cli --no-default-features --features postgres +cargo install sqlx-cli --no-default-features --features postgres ``` Check the contents of the `.env` file. If your database requires a password, update `DATABASE_URL` to be of the form: ```.env -DATABASE_URL=postgres://username:password@localhost/actix_todo +DATABASE_URL=postgres://username:password@localhost:5432/actix_todo ``` Then to create and set-up the database run: ```bash -diesel database setup +sqlx database create +sqlx migrate run ``` ## Run the application @@ -45,4 +46,4 @@ To run the application execute: cargo run ``` -Then to view it in your browser navigate to: [http://localhost:8080/](http://localhost:8080/) +Then to view it in your browser navigate to: [http://localhost:8088/](http://localhost:8088/) diff --git a/basics/todo/migrations/00000000000000_diesel_initial_setup/down.sql b/basics/todo/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f5260..0000000 --- a/basics/todo/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/basics/todo/migrations/00000000000000_diesel_initial_setup/up.sql b/basics/todo/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b..0000000 --- a/basics/todo/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/basics/todo/migrations/2018-07-05-163612_create_tasks_table/down.sql b/basics/todo/migrations/2018-07-05-163612_create_tasks_table/down.sql deleted file mode 100644 index a76b653..0000000 --- a/basics/todo/migrations/2018-07-05-163612_create_tasks_table/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE tasks diff --git a/basics/todo/migrations/2018-07-05-163612_create_tasks_table/up.sql b/basics/todo/migrations/20220205163207_create_tasks_table.sql similarity index 100% rename from basics/todo/migrations/2018-07-05-163612_create_tasks_table/up.sql rename to basics/todo/migrations/20220205163207_create_tasks_table.sql diff --git a/basics/todo/src/api.rs b/basics/todo/src/api.rs index b9e9dde..491286b 100644 --- a/basics/todo/src/api.rs +++ b/basics/todo/src/api.rs @@ -1,9 +1,9 @@ use actix_files::NamedFile; use actix_session::Session; -use actix_web::{ - dev, error, http, middleware::ErrorHandlerResponse, web, Error, HttpResponse, Result, -}; +use actix_web::middleware::ErrorHandlerResponse; +use actix_web::{dev, error, http, web, Error, HttpResponse, Result}; use serde::Deserialize; +use sqlx::postgres::PgPool; use tera::{Context, Tera}; use crate::{ @@ -12,11 +12,13 @@ use crate::{ }; pub async fn index( - pool: web::Data, + pool: web::Data, tmpl: web::Data, session: Session, ) -> Result { - let tasks = web::block(move || db::get_all_tasks(&pool)).await?; + let tasks = db::get_all_tasks(&pool) + .await + .map_err(error::ErrorInternalServerError)?; let mut context = Context::new(); context.insert("tasks", &tasks); @@ -42,7 +44,7 @@ pub struct CreateForm { pub async fn create( params: web::Form, - pool: web::Data, + pool: web::Data, session: Session, ) -> Result { if params.description.is_empty() { @@ -52,8 +54,8 @@ pub async fn create( )?; Ok(redirect_to("/")) } else { - web::block(move || db::create_task(params.into_inner().description, &pool)) - .await? + db::create_task(params.into_inner().description, &pool) + .await .map_err(error::ErrorInternalServerError)?; session::set_flash(&session, FlashMessage::success("Task successfully added"))?; Ok(redirect_to("/")) @@ -71,7 +73,7 @@ pub struct UpdateForm { } pub async fn update( - db: web::Data, + db: web::Data, params: web::Path, form: web::Form, session: Session, @@ -87,22 +89,22 @@ pub async fn update( } async fn toggle( - pool: web::Data, + pool: web::Data, params: web::Path, ) -> Result { - web::block(move || db::toggle_task(params.id, &pool)) - .await? + db::toggle_task(params.id, &pool) + .await .map_err(error::ErrorInternalServerError)?; Ok(redirect_to("/")) } async fn delete( - pool: web::Data, + pool: web::Data, params: web::Path, session: Session, ) -> Result { - web::block(move || db::delete_task(params.id, &pool)) - .await? + db::delete_task(params.id, &pool) + .await .map_err(error::ErrorInternalServerError)?; session::set_flash(&session, FlashMessage::success("Task was deleted."))?; Ok(redirect_to("/")) diff --git a/basics/todo/src/db.rs b/basics/todo/src/db.rs index 21515ff..0b5c702 100644 --- a/basics/todo/src/db.rs +++ b/basics/todo/src/db.rs @@ -1,43 +1,36 @@ -use std::ops::Deref as _; - -use diesel::{ - pg::PgConnection, - r2d2::{ConnectionManager, Pool, PoolError, PooledConnection}, -}; +use sqlx::postgres::{PgPool, PgPoolOptions}; use crate::model::{NewTask, Task}; -pub type PgPool = Pool>; -type PgPooledConnection = PooledConnection>; - -pub fn init_pool(database_url: &str) -> Result { - let manager = ConnectionManager::::new(database_url); - Pool::builder().build(manager) +pub async fn init_pool(database_url: &str) -> Result { + PgPoolOptions::new() + .connect_timeout(std::time::Duration::from_secs(2)) + .connect(database_url) + .await } -fn get_conn(pool: &PgPool) -> Result { - pool.get().map_err(|_| "Can't get connection") +pub async fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { + Task::all(pool).await.map_err(|_| "Error retrieving tasks") } -pub fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { - Task::all(get_conn(pool)?.deref()).map_err(|_| "Error retrieving tasks") -} - -pub fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { +pub async fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { let new_task = NewTask { description: todo }; - Task::insert(new_task, get_conn(pool)?.deref()) + Task::insert(new_task, pool) + .await .map(|_| ()) .map_err(|_| "Error inserting task") } -pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { - Task::toggle_with_id(id, get_conn(pool)?.deref()) +pub async fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::toggle_with_id(id, pool) + .await .map(|_| ()) .map_err(|_| "Error toggling task completion") } -pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { - Task::delete_with_id(id, get_conn(pool)?.deref()) +pub async fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::delete_with_id(id, pool) + .await .map(|_| ()) .map_err(|_| "Error deleting task") } diff --git a/basics/todo/src/main.rs b/basics/todo/src/main.rs index 3d1179b..0704017 100644 --- a/basics/todo/src/main.rs +++ b/basics/todo/src/main.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate diesel; - use std::{env, io}; use actix_files::Files; @@ -16,7 +13,6 @@ use tera::Tera; mod api; mod db; mod model; -mod schema; mod session; static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; @@ -28,9 +24,12 @@ async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let pool = db::init_pool(&database_url).expect("Failed to create pool"); + println!("{}", database_url); + let pool = db::init_pool(&database_url) + .await + .expect("Failed to create pool"); - log::info!("starting HTTP serer at http://localhost:8080"); + log::info!("starting HTTP serer at http://localhost:8088"); HttpServer::new(move || { log::debug!("Constructing the App"); @@ -50,8 +49,8 @@ async fn main() -> io::Result<()> { .handler(http::StatusCode::NOT_FOUND, api::not_found); App::new() - .app_data(templates) - .app_data(pool.clone()) + .app_data(web::Data::new(templates)) + .app_data(web::Data::new(pool.clone())) .wrap(Logger::default()) .wrap(session_store) .wrap(error_handlers) diff --git a/basics/todo/src/model.rs b/basics/todo/src/model.rs index 6fa1dc2..e183d81 100644 --- a/basics/todo/src/model.rs +++ b/basics/todo/src/model.rs @@ -1,19 +1,11 @@ -use diesel::pg::PgConnection; -use diesel::prelude::*; -use serde::Serialize; +use sqlx::PgPool; -use crate::schema::{ - tasks, - tasks::dsl::{completed as task_completed, tasks as all_tasks}, -}; - -#[derive(Debug, Insertable)] -#[table_name = "tasks"] +#[derive(Debug)] pub struct NewTask { pub description: String, } -#[derive(Debug, Queryable, Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct Task { pub id: i32, pub description: String, @@ -21,27 +13,62 @@ pub struct Task { } impl Task { - pub fn all(conn: &PgConnection) -> QueryResult> { - all_tasks.order(tasks::id.desc()).load::(conn) + pub async fn all(connection: &PgPool) -> Result, sqlx::Error> { + let tasks = sqlx::query_as!( + Task, + r#" + SELECT * + FROM tasks + "# + ) + .fetch_all(connection) + .await?; + Ok(tasks) } - pub fn insert(todo: NewTask, conn: &PgConnection) -> QueryResult { - diesel::insert_into(tasks::table) - .values(&todo) - .execute(conn) + pub async fn insert(todo: NewTask, connection: &PgPool) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO tasks (description) + VALUES ($1) + "#, + todo.description, + ) + .execute(connection) + .await?; + Ok(()) } - pub fn toggle_with_id(id: i32, conn: &PgConnection) -> QueryResult { - let task = all_tasks.find(id).get_result::(conn)?; - - let new_status = !task.completed; - let updated_task = diesel::update(all_tasks.find(id)); - updated_task - .set(task_completed.eq(new_status)) - .execute(conn) + pub async fn toggle_with_id( + id: i32, + connection: &PgPool, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + UPDATE tasks + SET completed = NOT completed + WHERE id = $1 + "#, + id + ) + .execute(connection) + .await?; + Ok(()) } - pub fn delete_with_id(id: i32, conn: &PgConnection) -> QueryResult { - diesel::delete(all_tasks.find(id)).execute(conn) + pub async fn delete_with_id( + id: i32, + connection: &PgPool, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + DELETE FROM tasks + WHERE id = $1 + "#, + id + ) + .execute(connection) + .await?; + Ok(()) } } diff --git a/basics/todo/src/schema.rs b/basics/todo/src/schema.rs deleted file mode 100644 index 246b05f..0000000 --- a/basics/todo/src/schema.rs +++ /dev/null @@ -1,7 +0,0 @@ -table! { - tasks (id) { - id -> Int4, - description -> Varchar, - completed -> Bool, - } -}