diff --git a/actix-web/README.md b/actix-web/README.md new file mode 100644 index 00000000..0c2802ba --- /dev/null +++ b/actix-web/README.md @@ -0,0 +1,5 @@ +# Actix Web with shuttle + +Normally one would configure an application with [Actix Web](https://docs.rs/actix-web/latest/actix_web/index.html) using the [App](https://docs.rs/actix-web/latest/actix_web/struct.App.html) struct. However, shuttle needs to move the users configuration across threads to start the server on our backend, and the `App` struct is `!Send` and `!Sync`. + +That means that for shuttle to support Actix Web, we need to use the [ServiceConfig](https://docs.rs/actix-web/latest/actix_web/web/struct.ServiceConfig.html) struct. You should be able to configure your application like you normally would, but some steps may be a bit different. If you do you find something that you would expect to be possible not working, please reach out and let us know. diff --git a/actix-web/hello-world/Cargo.toml b/actix-web/hello-world/Cargo.toml index b7c812f8..8bdddcb3 100644 --- a/actix-web/hello-world/Cargo.toml +++ b/actix-web/hello-world/Cargo.toml @@ -4,7 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -actix-service = "2.0.2" actix-web = "4.2.1" shuttle-service = { version = '0.8.0', features = ["web-actix-web"] } - diff --git a/actix-web/hello-world/src/lib.rs b/actix-web/hello-world/src/lib.rs index 528f4b37..a78719db 100644 --- a/actix-web/hello-world/src/lib.rs +++ b/actix-web/hello-world/src/lib.rs @@ -1,14 +1,15 @@ -use actix_web::web::{resource, ServiceConfig}; +use actix_web::{get, web::ServiceConfig}; use shuttle_service::ShuttleActixWeb; +#[get("/hello")] async fn hello_world() -> &'static str { "Hello World!" } #[shuttle_service::main] async fn actix_web( -) -> ShuttleActixWeb { +) -> ShuttleActixWeb { Ok(move |cfg: &mut ServiceConfig| { - cfg.service(resource("/hello").to(hello_world)); + cfg.service(hello_world); }) } diff --git a/actix-web/postgres/Cargo.toml b/actix-web/postgres/Cargo.toml new file mode 100644 index 00000000..27aa3d3a --- /dev/null +++ b/actix-web/postgres/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "postgres" +version = "0.1.0" +edition = "2021" + +[lib] + +[dependencies] +actix-web = "4.2.1" +serde = "1.0.148" +shuttle-service = { version = "0.8.0", features = ["web-actix-web"] } +shuttle-shared-db = { version = "0.8.0", features = ["postgres"] } +sqlx = { version = "0.6.2", features = ["runtime-tokio-native-tls", "postgres"] } diff --git a/actix-web/postgres/schema.sql b/actix-web/postgres/schema.sql new file mode 100644 index 00000000..460e7c23 --- /dev/null +++ b/actix-web/postgres/schema.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS todos; + +CREATE TABLE todos ( + id serial PRIMARY KEY, + note TEXT NOT NULL +); diff --git a/actix-web/postgres/src/lib.rs b/actix-web/postgres/src/lib.rs new file mode 100644 index 00000000..3e18ae7f --- /dev/null +++ b/actix-web/postgres/src/lib.rs @@ -0,0 +1,68 @@ +use actix_web::middleware::Logger; +use actix_web::{ + error, get, post, + web::{self, Json, ServiceConfig}, + Result, +}; +use serde::{Deserialize, Serialize}; +use shuttle_service::{error::CustomError, ShuttleActixWeb}; +use sqlx::{Executor, FromRow, PgPool}; + +#[get("/{id}")] +async fn retrieve(path: web::Path, state: web::Data) -> Result> { + let todo = sqlx::query_as("SELECT * FROM todos WHERE id = $1") + .bind(*path) + .fetch_one(&state.pool) + .await + .map_err(|e| error::ErrorBadRequest(e.to_string()))?; + + Ok(Json(todo)) +} + +#[post("")] +async fn add(todo: web::Json, state: web::Data) -> Result> { + let todo = sqlx::query_as("INSERT INTO todos(note) VALUES ($1) RETURNING id, note") + .bind(&todo.note) + .fetch_one(&state.pool) + .await + .map_err(|e| error::ErrorBadRequest(e.to_string()))?; + + Ok(Json(todo)) +} + +#[derive(Clone)] +struct AppState { + pool: PgPool, +} + +#[shuttle_service::main] +async fn actix_web( + #[shuttle_shared_db::Postgres] pool: PgPool, +) -> ShuttleActixWeb { + pool.execute(include_str!("../schema.sql")) + .await + .map_err(CustomError::new)?; + + let state = web::Data::new(AppState { pool }); + + Ok(move |cfg: &mut ServiceConfig| { + cfg.service( + web::scope("/todos") + .wrap(Logger::default()) + .service(retrieve) + .service(add) + .app_data(state), + ); + }) +} + +#[derive(Deserialize)] +struct TodoNew { + pub note: String, +} + +#[derive(Serialize, Deserialize, FromRow)] +struct Todo { + pub id: i32, + pub note: String, +}