1
0
mirror of https://github.com/actix/examples synced 2024-11-23 14:31:07 +01:00

Updated basics/to-do to v4 using sqlx.

This commit is contained in:
Chris Gubbin 2022-02-05 17:34:43 +00:00
parent ae9a9a0382
commit 2aa8b15ce2
11 changed files with 110 additions and 132 deletions

View File

@ -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"

View File

@ -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/)

View File

@ -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();

View File

@ -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;

View File

@ -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<db::PgPool>,
pool: web::Data<PgPool>,
tmpl: web::Data<Tera>,
session: Session,
) -> Result<HttpResponse, Error> {
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<CreateForm>,
pool: web::Data<db::PgPool>,
pool: web::Data<PgPool>,
session: Session,
) -> Result<HttpResponse, Error> {
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::PgPool>,
db: web::Data<PgPool>,
params: web::Path<UpdateParams>,
form: web::Form<UpdateForm>,
session: Session,
@ -87,22 +89,22 @@ pub async fn update(
}
async fn toggle(
pool: web::Data<db::PgPool>,
pool: web::Data<PgPool>,
params: web::Path<UpdateParams>,
) -> Result<HttpResponse, Error> {
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<db::PgPool>,
pool: web::Data<PgPool>,
params: web::Path<UpdateParams>,
session: Session,
) -> Result<HttpResponse, Error> {
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("/"))

View File

@ -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<ConnectionManager<PgConnection>>;
type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
pub fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
let manager = ConnectionManager::<PgConnection>::new(database_url);
Pool::builder().build(manager)
pub async fn init_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.connect_timeout(std::time::Duration::from_secs(2))
.connect(database_url)
.await
}
fn get_conn(pool: &PgPool) -> Result<PgPooledConnection, &'static str> {
pool.get().map_err(|_| "Can't get connection")
pub async fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'static str> {
Task::all(pool).await.map_err(|_| "Error retrieving tasks")
}
pub fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'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")
}

View File

@ -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)

View File

@ -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<Vec<Task>> {
all_tasks.order(tasks::id.desc()).load::<Task>(conn)
pub async fn all(connection: &PgPool) -> Result<Vec<Task>, 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<usize> {
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<usize> {
let task = all_tasks.find(id).get_result::<Task>(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<usize> {
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(())
}
}

View File

@ -1,7 +0,0 @@
table! {
tasks (id) {
id -> Int4,
description -> Varchar,
completed -> Bool,
}
}