mirror of
https://github.com/actix/examples
synced 2024-11-27 16:02:57 +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"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.0.0-rc.1"
|
actix-web = "4.0.0-rc.2"
|
||||||
actix-files = "0.6.0-beta.15"
|
actix-files = "0.6.0-beta.16"
|
||||||
actix-session = "0.5.0-beta.7"
|
actix-session = "0.5.0-beta.7"
|
||||||
|
|
||||||
diesel = { version = "1.3.2", features = ["postgres", "r2d2"] }
|
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures = "0.3.7"
|
futures = "0.3.7"
|
||||||
@ -16,3 +15,10 @@ log = "0.4"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tera = "1.5"
|
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
|
## 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
|
```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:
|
Check the contents of the `.env` file. If your database requires a password, update `DATABASE_URL` to be of the form:
|
||||||
|
|
||||||
```.env
|
```.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:
|
Then to create and set-up the database run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
diesel database setup
|
sqlx database create
|
||||||
|
sqlx migrate run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run the application
|
## Run the application
|
||||||
@ -45,4 +46,4 @@ To run the application execute:
|
|||||||
cargo run
|
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_files::NamedFile;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::{
|
use actix_web::middleware::ErrorHandlerResponse;
|
||||||
dev, error, http, middleware::ErrorHandlerResponse, web, Error, HttpResponse, Result,
|
use actix_web::{dev, error, http, web, Error, HttpResponse, Result};
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use sqlx::postgres::PgPool;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -12,11 +12,13 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
pool: web::Data<db::PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
tmpl: web::Data<Tera>,
|
tmpl: web::Data<Tera>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> 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();
|
let mut context = Context::new();
|
||||||
context.insert("tasks", &tasks);
|
context.insert("tasks", &tasks);
|
||||||
@ -42,7 +44,7 @@ pub struct CreateForm {
|
|||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
params: web::Form<CreateForm>,
|
params: web::Form<CreateForm>,
|
||||||
pool: web::Data<db::PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
if params.description.is_empty() {
|
if params.description.is_empty() {
|
||||||
@ -52,8 +54,8 @@ pub async fn create(
|
|||||||
)?;
|
)?;
|
||||||
Ok(redirect_to("/"))
|
Ok(redirect_to("/"))
|
||||||
} else {
|
} else {
|
||||||
web::block(move || db::create_task(params.into_inner().description, &pool))
|
db::create_task(params.into_inner().description, &pool)
|
||||||
.await?
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
session::set_flash(&session, FlashMessage::success("Task successfully added"))?;
|
session::set_flash(&session, FlashMessage::success("Task successfully added"))?;
|
||||||
Ok(redirect_to("/"))
|
Ok(redirect_to("/"))
|
||||||
@ -71,7 +73,7 @@ pub struct UpdateForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
db: web::Data<db::PgPool>,
|
db: web::Data<PgPool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
form: web::Form<UpdateForm>,
|
form: web::Form<UpdateForm>,
|
||||||
session: Session,
|
session: Session,
|
||||||
@ -87,22 +89,22 @@ pub async fn update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn toggle(
|
async fn toggle(
|
||||||
pool: web::Data<db::PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
web::block(move || db::toggle_task(params.id, &pool))
|
db::toggle_task(params.id, &pool)
|
||||||
.await?
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
Ok(redirect_to("/"))
|
Ok(redirect_to("/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(
|
async fn delete(
|
||||||
pool: web::Data<db::PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
web::block(move || db::delete_task(params.id, &pool))
|
db::delete_task(params.id, &pool)
|
||||||
.await?
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
session::set_flash(&session, FlashMessage::success("Task was deleted."))?;
|
session::set_flash(&session, FlashMessage::success("Task was deleted."))?;
|
||||||
Ok(redirect_to("/"))
|
Ok(redirect_to("/"))
|
||||||
|
@ -1,43 +1,36 @@
|
|||||||
use std::ops::Deref as _;
|
use sqlx::postgres::{PgPool, PgPoolOptions};
|
||||||
|
|
||||||
use diesel::{
|
|
||||||
pg::PgConnection,
|
|
||||||
r2d2::{ConnectionManager, Pool, PoolError, PooledConnection},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::model::{NewTask, Task};
|
use crate::model::{NewTask, Task};
|
||||||
|
|
||||||
pub type PgPool = Pool<ConnectionManager<PgConnection>>;
|
pub async fn init_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
|
||||||
type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
|
PgPoolOptions::new()
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(2))
|
||||||
pub fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
|
.connect(database_url)
|
||||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
.await
|
||||||
Pool::builder().build(manager)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_conn(pool: &PgPool) -> Result<PgPooledConnection, &'static str> {
|
pub async fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'static str> {
|
||||||
pool.get().map_err(|_| "Can't get connection")
|
Task::all(pool).await.map_err(|_| "Error retrieving tasks")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'static str> {
|
pub async fn create_task(todo: String, 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> {
|
|
||||||
let new_task = NewTask { description: todo };
|
let new_task = NewTask { description: todo };
|
||||||
Task::insert(new_task, get_conn(pool)?.deref())
|
Task::insert(new_task, pool)
|
||||||
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| "Error inserting task")
|
.map_err(|_| "Error inserting task")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
|
pub async fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
|
||||||
Task::toggle_with_id(id, get_conn(pool)?.deref())
|
Task::toggle_with_id(id, pool)
|
||||||
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| "Error toggling task completion")
|
.map_err(|_| "Error toggling task completion")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
|
pub async fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
|
||||||
Task::delete_with_id(id, get_conn(pool)?.deref())
|
Task::delete_with_id(id, pool)
|
||||||
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| "Error deleting task")
|
.map_err(|_| "Error deleting task")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate diesel;
|
|
||||||
|
|
||||||
use std::{env, io};
|
use std::{env, io};
|
||||||
|
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
@ -16,7 +13,6 @@ use tera::Tera;
|
|||||||
mod api;
|
mod api;
|
||||||
mod db;
|
mod db;
|
||||||
mod model;
|
mod model;
|
||||||
mod schema;
|
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
static SESSION_SIGNING_KEY: &[u8] = &[0; 32];
|
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"));
|
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 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 || {
|
HttpServer::new(move || {
|
||||||
log::debug!("Constructing the App");
|
log::debug!("Constructing the App");
|
||||||
@ -50,8 +49,8 @@ async fn main() -> io::Result<()> {
|
|||||||
.handler(http::StatusCode::NOT_FOUND, api::not_found);
|
.handler(http::StatusCode::NOT_FOUND, api::not_found);
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(templates)
|
.app_data(web::Data::new(templates))
|
||||||
.app_data(pool.clone())
|
.app_data(web::Data::new(pool.clone()))
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(session_store)
|
.wrap(session_store)
|
||||||
.wrap(error_handlers)
|
.wrap(error_handlers)
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
use diesel::pg::PgConnection;
|
use sqlx::PgPool;
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::schema::{
|
#[derive(Debug)]
|
||||||
tasks,
|
|
||||||
tasks::dsl::{completed as task_completed, tasks as all_tasks},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Insertable)]
|
|
||||||
#[table_name = "tasks"]
|
|
||||||
pub struct NewTask {
|
pub struct NewTask {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Queryable, Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@ -21,27 +13,62 @@ pub struct Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn all(conn: &PgConnection) -> QueryResult<Vec<Task>> {
|
pub async fn all(connection: &PgPool) -> Result<Vec<Task>, sqlx::Error> {
|
||||||
all_tasks.order(tasks::id.desc()).load::<Task>(conn)
|
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> {
|
pub async fn insert(todo: NewTask, connection: &PgPool) -> Result<(), sqlx::Error> {
|
||||||
diesel::insert_into(tasks::table)
|
sqlx::query!(
|
||||||
.values(&todo)
|
r#"
|
||||||
.execute(conn)
|
INSERT INTO tasks (description)
|
||||||
|
VALUES ($1)
|
||||||
|
"#,
|
||||||
|
todo.description,
|
||||||
|
)
|
||||||
|
.execute(connection)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_with_id(id: i32, conn: &PgConnection) -> QueryResult<usize> {
|
pub async fn toggle_with_id(
|
||||||
let task = all_tasks.find(id).get_result::<Task>(conn)?;
|
id: i32,
|
||||||
|
connection: &PgPool,
|
||||||
let new_status = !task.completed;
|
) -> Result<(), sqlx::Error> {
|
||||||
let updated_task = diesel::update(all_tasks.find(id));
|
sqlx::query!(
|
||||||
updated_task
|
r#"
|
||||||
.set(task_completed.eq(new_status))
|
UPDATE tasks
|
||||||
.execute(conn)
|
SET completed = NOT completed
|
||||||
|
WHERE id = $1
|
||||||
|
"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(connection)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_with_id(id: i32, conn: &PgConnection) -> QueryResult<usize> {
|
pub async fn delete_with_id(
|
||||||
diesel::delete(all_tasks.find(id)).execute(conn)
|
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