mirror of
https://github.com/actix/examples
synced 2025-02-17 07:23:29 +01:00
use sqlite in basics/todo
This commit is contained in:
parent
a72663ed26
commit
120d33057a
@ -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
2
basics/todo/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
todo.db
|
||||||
|
todo.db-*
|
@ -10,15 +10,12 @@ actix-session = "0.5.0-beta.7"
|
|||||||
|
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures = "0.3.7"
|
futures-util = "0.3.7"
|
||||||
log = "0.4"
|
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]
|
[dependencies.sqlx]
|
||||||
features = [
|
features = ["runtime-tokio-rustls", "sqlite", "offline"]
|
||||||
"runtime-actix-rustls",
|
version = "0.5.10"
|
||||||
"postgres"
|
|
||||||
]
|
|
||||||
version = "0.5.10"
|
|
||||||
|
@ -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.
|
A simple Todo project using a SQLite database.
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* Rust >= 1.26
|
- SQLite 3
|
||||||
* PostgreSQL >= 9.5
|
|
||||||
|
|
||||||
## Change into the project sub-directory
|
## Change Into This Project Sub Directory
|
||||||
|
|
||||||
All instructions assume you have changed into this folder:
|
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
|
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
|
```sh
|
||||||
cargo install sqlx-cli --no-default-features --features postgres
|
cargo install sqlx-cli --no-default-features --features=rustls,sqlite
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then to create and set-up the database run:
|
Then to create and set-up the database run:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
sqlx database create
|
sqlx database create
|
||||||
sqlx migrate run
|
sqlx migrate run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run the application
|
## Run The Application
|
||||||
|
|
||||||
To run the application execute:
|
Start the application with:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
cargo run
|
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
|
||||||
|
```
|
||||||
|
@ -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"
|
|
@ -1 +1 @@
|
|||||||
DROP TABLE tasks
|
DROP TABLE tasks;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
CREATE TABLE tasks (
|
CREATE TABLE tasks (
|
||||||
id SERIAL PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
description VARCHAR NOT NULL,
|
description VARCHAR NOT NULL,
|
||||||
completed BOOLEAN NOT NULL DEFAULT 'f'
|
completed BOOLEAN NOT NULL DEFAULT 'f'
|
||||||
);
|
);
|
||||||
|
63
basics/todo/sqlx-data.json
Normal file
63
basics/todo/sqlx-data.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::middleware::ErrorHandlerResponse;
|
use actix_web::{
|
||||||
use actix_web::{dev, error, http, web, Error, HttpResponse, Result};
|
dev, error, http, middleware::ErrorHandlerResponse, web, Error, HttpResponse, Result,
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::SqlitePool;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -12,7 +13,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<SqlitePool>,
|
||||||
tmpl: web::Data<Tera>,
|
tmpl: web::Data<Tera>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
@ -44,7 +45,7 @@ pub struct CreateForm {
|
|||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
params: web::Form<CreateForm>,
|
params: web::Form<CreateForm>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<SqlitePool>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
if params.description.is_empty() {
|
if params.description.is_empty() {
|
||||||
@ -73,7 +74,7 @@ pub struct UpdateForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
db: web::Data<PgPool>,
|
db: web::Data<SqlitePool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
form: web::Form<UpdateForm>,
|
form: web::Form<UpdateForm>,
|
||||||
session: Session,
|
session: Session,
|
||||||
@ -89,7 +90,7 @@ pub async fn update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn toggle(
|
async fn toggle(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<SqlitePool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
db::toggle_task(params.id, &pool)
|
db::toggle_task(params.id, &pool)
|
||||||
@ -99,7 +100,7 @@ async fn toggle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(
|
async fn delete(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<SqlitePool>,
|
||||||
params: web::Path<UpdateParams>,
|
params: web::Path<UpdateParams>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
use sqlx::postgres::{PgPool, PgPoolOptions};
|
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
|
|
||||||
use crate::model::{NewTask, Task};
|
use crate::model::{NewTask, Task};
|
||||||
|
|
||||||
pub async fn init_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
|
pub async fn init_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
|
||||||
PgPoolOptions::new()
|
SqlitePoolOptions::new()
|
||||||
.connect_timeout(std::time::Duration::from_secs(2))
|
.connect_timeout(std::time::Duration::from_secs(1))
|
||||||
.connect(database_url)
|
.connect(database_url)
|
||||||
.await
|
.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")
|
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 };
|
let new_task = NewTask { description: todo };
|
||||||
Task::insert(new_task, pool)
|
Task::insert(new_task, pool)
|
||||||
.await
|
.await
|
||||||
@ -21,14 +21,14 @@ pub async fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str
|
|||||||
.map_err(|_| "Error inserting task")
|
.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)
|
Task::toggle_with_id(id, pool)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| "Error toggling task completion")
|
.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)
|
Task::delete_with_id(id, pool)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
|
@ -20,16 +20,14 @@ static SESSION_SIGNING_KEY: &[u8] = &[0; 32];
|
|||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
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");
|
||||||
println!("{}", database_url);
|
|
||||||
let pool = db::init_pool(&database_url)
|
let pool = db::init_pool(&database_url)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create pool");
|
.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 || {
|
HttpServer::new(move || {
|
||||||
log::debug!("Constructing the App");
|
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(web::resource("/todo/{id}").route(web::post().to(api::update)))
|
||||||
.service(Files::new("/static", "./static/"))
|
.service(Files::new("/static", "./static/"))
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8088))?
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.workers(2)
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
use sqlx::PgPool;
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NewTask {
|
pub struct NewTask {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub id: i32,
|
pub id: i64,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub completed: bool,
|
pub completed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
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!(
|
let tasks = sqlx::query_as!(
|
||||||
Task,
|
Task,
|
||||||
r#"
|
r#"
|
||||||
@ -23,10 +24,14 @@ impl Task {
|
|||||||
)
|
)
|
||||||
.fetch_all(connection)
|
.fetch_all(connection)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(tasks)
|
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!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO tasks (description)
|
INSERT INTO tasks (description)
|
||||||
@ -36,29 +41,31 @@ impl Task {
|
|||||||
)
|
)
|
||||||
.execute(connection)
|
.execute(connection)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn toggle_with_id(
|
pub async fn toggle_with_id(
|
||||||
id: i32,
|
id: i32,
|
||||||
connection: &PgPool,
|
connection: &SqlitePool,
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE tasks
|
UPDATE tasks
|
||||||
SET completed = NOT completed
|
SET completed = NOT completed
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.execute(connection)
|
.execute(connection)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_with_id(
|
pub async fn delete_with_id(
|
||||||
id: i32,
|
id: i32,
|
||||||
connection: &PgPool,
|
connection: &SqlitePool,
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
@ -69,6 +76,7 @@ impl Task {
|
|||||||
)
|
)
|
||||||
.execute(connection)
|
.execute(connection)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user