Implement server endpoints

This commit is contained in:
2025-11-13 12:42:50 +03:00
parent 6eab539107
commit a403134ab2
5 changed files with 1405 additions and 19 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
.env

1270
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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