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:
parent
ae9a9a0382
commit
2aa8b15ce2
@ -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"
|
@ -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/)
|
||||
|
@ -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();
|
@ -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;
|
@ -1 +0,0 @@
|
||||
DROP TABLE tasks
|
@ -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("/"))
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
table! {
|
||||
tasks (id) {
|
||||
id -> Int4,
|
||||
description -> Varchar,
|
||||
completed -> Bool,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user