diff --git a/basics/todo/.env b/basics/todo/.env index ce7ff18..0be563c 100644 --- a/basics/todo/.env +++ b/basics/todo/.env @@ -1 +1,3 @@ -DATABASE_URL=postgres://localhost/actix_todo +DATABASE_URL=sqlite://${CARGO_MANIFEST_DIR}/todo.db +SQLX_OFFLINE=true +RUST_LOG=info diff --git a/basics/todo/.gitignore b/basics/todo/.gitignore new file mode 100644 index 0000000..e2f287b --- /dev/null +++ b/basics/todo/.gitignore @@ -0,0 +1,2 @@ +todo.db +todo.db-* diff --git a/basics/todo/Cargo.toml b/basics/todo/Cargo.toml index 7d8a393..94bd101 100644 --- a/basics/todo/Cargo.toml +++ b/basics/todo/Cargo.toml @@ -10,15 +10,12 @@ actix-session = "0.5.0-beta.7" dotenv = "0.15" env_logger = "0.9" -futures = "0.3.7" +futures-util = "0.3.7" 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 +features = ["runtime-tokio-rustls", "sqlite", "offline"] +version = "0.5.10" diff --git a/basics/todo/README.md b/basics/todo/README.md index 1f31c8d..737c384 100644 --- a/basics/todo/README.md +++ b/basics/todo/README.md @@ -1,15 +1,12 @@ -# actix-todo +# Todo -A port of the [Rocket Todo example](https://github.com/SergioBenitez/Rocket/tree/master/examples/todo) into [actix-web](https://actix.rs/). Except this uses PostgreSQL instead of SQLite. - -# Usage +A simple Todo project using a SQLite database. ## Prerequisites -* Rust >= 1.26 -* PostgreSQL >= 9.5 +- SQLite 3 -## Change into the project sub-directory +## Change Into This Project Sub Directory All instructions assume you have changed into this folder: @@ -17,33 +14,35 @@ All instructions assume you have changed into this folder: cd basics/todo ``` -## Set up the database +## Set Up The Database -Install the [sqlx](https://github.com/launchbadge/sqlx) command-line tool including the `postgres` feature: +Install the [sqlx](https://github.com/launchbadge/sqlx/tree/HEAD/sqlx-cli) command-line tool with the required features: -```bash -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:5432/actix_todo +```sh +cargo install sqlx-cli --no-default-features --features=rustls,sqlite ``` Then to create and set-up the database run: -```bash +```sh sqlx database create sqlx migrate run ``` -## Run the application +## Run The Application -To run the application execute: +Start the application with: -```bash +```sh cargo run ``` -Then to view it in your browser navigate to: [http://localhost:8088/](http://localhost:8088/) +The app will be viewable in the browser at . + +## Modifying The Example Database + +For simplicity, this example uses SQLx's offline mode. If you make any changes to the database schema, this must be turned off or the `sqlx-data.json` file regenerated using the following command: + +```sh +DATABASE_URL="sqlite://$(pwd)/todo.db" cargo sqlx prepare +``` diff --git a/basics/todo/diesel.toml b/basics/todo/diesel.toml deleted file mode 100644 index 92267c8..0000000 --- a/basics/todo/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema.rs" diff --git a/basics/todo/migrations/.gitkeep b/basics/todo/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/basics/todo/migrations/20220205163207_create_tasks_table.down.sql b/basics/todo/migrations/20220205163207_create_tasks_table.down.sql index a76b653..87f1ed5 100644 --- a/basics/todo/migrations/20220205163207_create_tasks_table.down.sql +++ b/basics/todo/migrations/20220205163207_create_tasks_table.down.sql @@ -1 +1 @@ -DROP TABLE tasks +DROP TABLE tasks; diff --git a/basics/todo/migrations/20220205163207_create_tasks_table.up.sql b/basics/todo/migrations/20220205163207_create_tasks_table.up.sql index 3501273..20d5bd2 100644 --- a/basics/todo/migrations/20220205163207_create_tasks_table.up.sql +++ b/basics/todo/migrations/20220205163207_create_tasks_table.up.sql @@ -1,5 +1,5 @@ CREATE TABLE tasks ( - id SERIAL PRIMARY KEY, + id INTEGER PRIMARY KEY, description VARCHAR NOT NULL, completed BOOLEAN NOT NULL DEFAULT 'f' ); diff --git a/basics/todo/sqlx-data.json b/basics/todo/sqlx-data.json new file mode 100644 index 0000000..99c06ab --- /dev/null +++ b/basics/todo/sqlx-data.json @@ -0,0 +1,63 @@ +{ + "db": "SQLite", + "2f4086eab47b13298a107ed8c0feaadab903468b8e4fbd39e465f581c7189272": { + "query": "\n DELETE FROM tasks\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + } + }, + "37443376215969e31bc6bc1ee00f01044647d95caf8efc61df7bf333b6a2149d": { + "query": "\n UPDATE tasks\n SET completed = NOT completed\n WHERE id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + } + }, + "3a5b2a81088aa91ade3106165f926c337a88f4e29ae3bb170c4b687208aba20d": { + "query": "\n SELECT *\n FROM tasks\n ", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int64" + }, + { + "name": "description", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "completed", + "ordinal": 2, + "type_info": "Bool" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false + ] + } + }, + "ab058898a8a1b13174d03c9972af33214619f8aa3080bc27f88bd5b9212b8c0f": { + "query": "\n INSERT INTO tasks (description)\n VALUES ($1)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + } + } +} \ No newline at end of file diff --git a/basics/todo/src/api.rs b/basics/todo/src/api.rs index 491286b..dc66fb7 100644 --- a/basics/todo/src/api.rs +++ b/basics/todo/src/api.rs @@ -1,9 +1,10 @@ use actix_files::NamedFile; use actix_session::Session; -use actix_web::middleware::ErrorHandlerResponse; -use actix_web::{dev, error, http, web, Error, HttpResponse, Result}; +use actix_web::{ + dev, error, http, middleware::ErrorHandlerResponse, web, Error, HttpResponse, Result, +}; use serde::Deserialize; -use sqlx::postgres::PgPool; +use sqlx::SqlitePool; use tera::{Context, Tera}; use crate::{ @@ -12,7 +13,7 @@ use crate::{ }; pub async fn index( - pool: web::Data, + pool: web::Data, tmpl: web::Data, session: Session, ) -> Result { @@ -44,7 +45,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() { @@ -73,7 +74,7 @@ pub struct UpdateForm { } pub async fn update( - db: web::Data, + db: web::Data, params: web::Path, form: web::Form, session: Session, @@ -89,7 +90,7 @@ pub async fn update( } async fn toggle( - pool: web::Data, + pool: web::Data, params: web::Path, ) -> Result { db::toggle_task(params.id, &pool) @@ -99,7 +100,7 @@ async fn toggle( } async fn delete( - pool: web::Data, + pool: web::Data, params: web::Path, session: Session, ) -> Result { diff --git a/basics/todo/src/db.rs b/basics/todo/src/db.rs index 0b5c702..abf26b1 100644 --- a/basics/todo/src/db.rs +++ b/basics/todo/src/db.rs @@ -1,19 +1,19 @@ -use sqlx::postgres::{PgPool, PgPoolOptions}; +use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; use crate::model::{NewTask, Task}; -pub async fn init_pool(database_url: &str) -> Result { - PgPoolOptions::new() - .connect_timeout(std::time::Duration::from_secs(2)) +pub async fn init_pool(database_url: &str) -> Result { + SqlitePoolOptions::new() + .connect_timeout(std::time::Duration::from_secs(1)) .connect(database_url) .await } -pub async fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { +pub async fn get_all_tasks(pool: &SqlitePool) -> Result, &'static str> { Task::all(pool).await.map_err(|_| "Error retrieving tasks") } -pub async fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { +pub async fn create_task(todo: String, pool: &SqlitePool) -> Result<(), &'static str> { let new_task = NewTask { description: todo }; Task::insert(new_task, pool) .await @@ -21,14 +21,14 @@ pub async fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str .map_err(|_| "Error inserting task") } -pub async fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { +pub async fn toggle_task(id: i32, pool: &SqlitePool) -> Result<(), &'static str> { Task::toggle_with_id(id, pool) .await .map(|_| ()) .map_err(|_| "Error toggling task completion") } -pub async fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { +pub async fn delete_task(id: i32, pool: &SqlitePool) -> Result<(), &'static str> { Task::delete_with_id(id, pool) .await .map(|_| ()) diff --git a/basics/todo/src/main.rs b/basics/todo/src/main.rs index 0704017..b216e3d 100644 --- a/basics/todo/src/main.rs +++ b/basics/todo/src/main.rs @@ -20,16 +20,14 @@ static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; #[actix_web::main] async fn main() -> io::Result<()> { dotenv().ok(); - 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"); - println!("{}", database_url); let pool = db::init_pool(&database_url) .await .expect("Failed to create pool"); - log::info!("starting HTTP serer at http://localhost:8088"); + log::info!("starting HTTP serer at http://localhost:8080"); HttpServer::new(move || { log::debug!("Constructing the App"); @@ -59,7 +57,8 @@ async fn main() -> io::Result<()> { .service(web::resource("/todo/{id}").route(web::post().to(api::update))) .service(Files::new("/static", "./static/")) }) - .bind(("127.0.0.1", 8088))? + .bind(("127.0.0.1", 8080))? + .workers(2) .run() .await } diff --git a/basics/todo/src/model.rs b/basics/todo/src/model.rs index e183d81..b9fa582 100644 --- a/basics/todo/src/model.rs +++ b/basics/todo/src/model.rs @@ -1,19 +1,20 @@ -use sqlx::PgPool; +use serde::{Deserialize, Serialize}; +use sqlx::SqlitePool; #[derive(Debug)] pub struct NewTask { pub description: String, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Task { - pub id: i32, + pub id: i64, pub description: String, pub completed: bool, } impl Task { - pub async fn all(connection: &PgPool) -> Result, sqlx::Error> { + pub async fn all(connection: &SqlitePool) -> Result, sqlx::Error> { let tasks = sqlx::query_as!( Task, r#" @@ -23,10 +24,14 @@ impl Task { ) .fetch_all(connection) .await?; + Ok(tasks) } - pub async fn insert(todo: NewTask, connection: &PgPool) -> Result<(), sqlx::Error> { + pub async fn insert( + todo: NewTask, + connection: &SqlitePool, + ) -> Result<(), sqlx::Error> { sqlx::query!( r#" INSERT INTO tasks (description) @@ -36,29 +41,31 @@ impl Task { ) .execute(connection) .await?; + Ok(()) } pub async fn toggle_with_id( id: i32, - connection: &PgPool, + connection: &SqlitePool, ) -> Result<(), sqlx::Error> { sqlx::query!( r#" UPDATE tasks SET completed = NOT completed WHERE id = $1 - "#, + "#, id ) .execute(connection) .await?; + Ok(()) } pub async fn delete_with_id( id: i32, - connection: &PgPool, + connection: &SqlitePool, ) -> Result<(), sqlx::Error> { sqlx::query!( r#" @@ -69,6 +76,7 @@ impl Task { ) .execute(connection) .await?; + Ok(()) } }