Implement server endpoints
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
.env
|
||||||
|
|||||||
1270
Cargo.lock
generated
1270
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,5 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.8.6"
|
axum = "0.8.6"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio"] }
|
||||||
tokio = { version = "1.48.0", features = ["full"] }
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
|
|||||||
9
compose.yaml
Normal file
9
compose.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: docker.io/postgres:17
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "my-server-db"
|
||||||
|
POSTGRES_USER: "l0vpre"
|
||||||
|
POSTGRES_PASSWORD: "buranya"
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
142
src/main.rs
142
src/main.rs
@ -1,26 +1,146 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Json, Router,
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{delete, get, post},
|
routing::{delete, get, post},
|
||||||
};
|
};
|
||||||
|
use sqlx::PgPool;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
pool: PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||||
|
struct Sandwich {
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
composition: String,
|
||||||
|
price: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct CreateSandwich {
|
||||||
|
name: String,
|
||||||
|
composition: String,
|
||||||
|
price: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct SandwichPathParams {
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let router = Router::new().nest(
|
let _ = dotenvy::dotenv();
|
||||||
"/sandwiches",
|
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL should be set");
|
||||||
Router::new()
|
println!("DATABASE_URL={database_url}");
|
||||||
.route("/", get(handler))
|
|
||||||
.route("/", post(handler))
|
let pool = PgPool::connect(&database_url)
|
||||||
.route("/{id}", post(handler))
|
.await
|
||||||
.route("/{id}", delete(handler)),
|
.expect("Could not connect to database");
|
||||||
);
|
sqlx::raw_sql(
|
||||||
|
r#"
|
||||||
|
-- DROP TABLE IF EXISTS sandwiches;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sandwiches(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
composition TEXT NOT NULL,
|
||||||
|
price INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Could not create database tables");
|
||||||
|
let state = AppState { pool };
|
||||||
|
let router = Router::new()
|
||||||
|
.nest(
|
||||||
|
"/sandwiches",
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(get_sandwiches))
|
||||||
|
.route("/", post(post_sandwiches))
|
||||||
|
.route("/{id}", get(get_sandwich_by_id))
|
||||||
|
.route("/{id}", post(post_sandwich_by_id))
|
||||||
|
.route("/{id}", delete(delete_sandwich_by_id)),
|
||||||
|
)
|
||||||
|
.with_state(state);
|
||||||
let addr = "0.0.0.0:8000";
|
let addr = "0.0.0.0:8000";
|
||||||
let listener = TcpListener::bind(addr).await.unwrap();
|
let listener = TcpListener::bind(addr).await.unwrap();
|
||||||
println!("Listener at {addr}");
|
println!("Listener at {addr}");
|
||||||
axum::serve(listener, router).await.unwrap();
|
axum::serve(listener, router).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handler() -> impl IntoResponse {
|
async fn post_sandwiches(
|
||||||
axum::http::StatusCode::NOT_IMPLEMENTED
|
State(state): State<AppState>,
|
||||||
|
Json(sandwich): Json<CreateSandwich>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
sqlx::query("INSERT INTO sandwiches(name, composition, price) VALUES($1, $2, $3)")
|
||||||
|
.bind(sandwich.name)
|
||||||
|
.bind(sandwich.composition)
|
||||||
|
.bind(sandwich.price)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
StatusCode::CREATED
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_sandwiches(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
let sanwiches: Vec<Sandwich> = sqlx::query_as("SELECT * FROM sandwiches")
|
||||||
|
.fetch_all(&state.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Json(sanwiches)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_sandwich_by_id(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(params): Path<SandwichPathParams>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let sandwich: Option<Sandwich> = sqlx::query_as("SELECT * FROM sandwiches WHERE id = $1")
|
||||||
|
.bind(params.id)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match sandwich {
|
||||||
|
Some(s) => Json(s).into_response(),
|
||||||
|
None => StatusCode::NOT_FOUND.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_sandwich_by_id(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(params): Path<SandwichPathParams>,
|
||||||
|
Json(sandwich): Json<CreateSandwich>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
sqlx::query("UPDATE sandwiches SET name = $1, composition = $2, price = $3 WHERE id = $4")
|
||||||
|
.bind(sandwich.name)
|
||||||
|
.bind(sandwich.composition)
|
||||||
|
.bind(sandwich.price)
|
||||||
|
.bind(params.id)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
StatusCode::CREATED
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_sandwich_by_id(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(params): Path<SandwichPathParams>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
sqlx::query("DELETE FROM sandwiches WHERE id = $1")
|
||||||
|
.bind(params.id)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
StatusCode::NO_CONTENT
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user