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:
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"
|
||||
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"
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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 (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY,
|
||||
description VARCHAR NOT NULL,
|
||||
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_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> {
|
||||
|
@ -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(|_| ())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user