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

use sqlite in basics/todo

This commit is contained in:
Rob Ede 2022-02-06 07:55:51 +00:00
parent a72663ed26
commit 120d33057a
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
13 changed files with 130 additions and 64 deletions

View File

@ -1 +1,3 @@
DATABASE_URL=postgres://localhost/actix_todo
DATABASE_URL=sqlite://${CARGO_MANIFEST_DIR}/todo.db
SQLX_OFFLINE=true
RUST_LOG=info

2
basics/todo/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
todo.db
todo.db-*

View File

@ -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"
features = ["runtime-tokio-rustls", "sqlite", "offline"]
version = "0.5.10"

View File

@ -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 <http://localhost:8080>.
## 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
```

View File

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

View File

@ -1 +1 @@
DROP TABLE tasks
DROP TABLE tasks;

View File

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

View File

@ -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": []
}
}
}

View File

@ -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<PgPool>,
pool: web::Data<SqlitePool>,
tmpl: web::Data<Tera>,
session: Session,
) -> Result<HttpResponse, Error> {
@ -44,7 +45,7 @@ pub struct CreateForm {
pub async fn create(
params: web::Form<CreateForm>,
pool: web::Data<PgPool>,
pool: web::Data<SqlitePool>,
session: Session,
) -> Result<HttpResponse, Error> {
if params.description.is_empty() {
@ -73,7 +74,7 @@ pub struct UpdateForm {
}
pub async fn update(
db: web::Data<PgPool>,
db: web::Data<SqlitePool>,
params: web::Path<UpdateParams>,
form: web::Form<UpdateForm>,
session: Session,
@ -89,7 +90,7 @@ pub async fn update(
}
async fn toggle(
pool: web::Data<PgPool>,
pool: web::Data<SqlitePool>,
params: web::Path<UpdateParams>,
) -> Result<HttpResponse, Error> {
db::toggle_task(params.id, &pool)
@ -99,7 +100,7 @@ async fn toggle(
}
async fn delete(
pool: web::Data<PgPool>,
pool: web::Data<SqlitePool>,
params: web::Path<UpdateParams>,
session: Session,
) -> Result<HttpResponse, Error> {

View File

@ -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<PgPool, sqlx::Error> {
PgPoolOptions::new()
.connect_timeout(std::time::Duration::from_secs(2))
pub async fn init_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
SqlitePoolOptions::new()
.connect_timeout(std::time::Duration::from_secs(1))
.connect(database_url)
.await
}
pub async fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'static str> {
pub async fn get_all_tasks(pool: &SqlitePool) -> Result<Vec<Task>, &'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(|_| ())

View File

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

View File

@ -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<Vec<Task>, sqlx::Error> {
pub async fn all(connection: &SqlitePool) -> Result<Vec<Task>, 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(())
}
}