Compare commits

..

4 Commits

18 changed files with 2583 additions and 3 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=sqlite://mydb.db

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
*.db

2356
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,19 @@
[package]
name = "user-server"
name = "hospital_server"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = {version = "0.8.4", features = ["macros"]}
tokio = {version = "1.47.1", features = ["full"]}
utoipa = "5.4.0"
sqlx = {version = "0.6.0", features = ["runtime-tokio-native-tls","sqlite"]}
config = "0.15.6"
utoipa-axum = {version = "0.2.0" }
utoipa-scalar = { version = "0.3", features = ["axum"] }
tracing = "0.1.41"
serde = "1.0.228"
tracing-subscriber = "0.3.20"
dotenvy = "0.15.7"

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# test_exercise
Небольшой тестовый репозиторий на раст для ликвидации skill issue

View File

@ -0,0 +1 @@
drop table "patients";

View File

@ -0,0 +1,5 @@
create table "patients" (
"id" INTEGER PRIMARY KEY,
"full_name" TEXT NOT NULL,
"phone" TEXT NOT NULL
);

View File

@ -0,0 +1,9 @@
insert into patients(
"full_name",
"phone"
)
values($1, $2)
returning
"id",
"full_name" as "full_name!",
"phone" as "phone!"

View File

@ -0,0 +1,3 @@
delete from patients
where id = $1
returning *;

1
queries/get_all.sql Normal file
View File

@ -0,0 +1 @@
select * from "patients"

View File

@ -0,0 +1,2 @@
select * from "patients"
where id = $1;

View File

@ -0,0 +1,3 @@
update patients
set full_name = $2, phone = $3
where id = $1;

6
src/app_state.rs Normal file
View File

@ -0,0 +1,6 @@
use sqlx::SqlitePool;
#[derive(Clone)]
pub struct AppState {
pub pool: SqlitePool,
}

117
src/lib.rs Normal file
View File

@ -0,0 +1,117 @@
use axum::{
Router, debug_handler,
extract::{Json, Path, State},
http::StatusCode,
response::IntoResponse,
};
use models::Patient;
use sqlx::{query_file, query_file_as};
use utoipa::OpenApi;
use utoipa_axum::{router::OpenApiRouter, routes};
use crate::{
app_state::AppState,
models::{CreatePatient, PathParams},
openapi::route_docs,
};
pub mod app_state;
pub mod models;
pub mod openapi;
pub mod schemas;
#[derive(OpenApi)]
struct Api;
pub fn router(state: AppState) -> Router {
let (router, docs) = OpenApiRouter::with_openapi(Api::openapi())
.nest(
"/api/patients/",
OpenApiRouter::new()
.routes(routes!(create_patient, get_all))
.routes(routes!(
update_patient_by_id,
delete_patient_by_id,
get_patient_by_id
)),
)
.with_state(state)
.split_for_parts();
route_docs(router, docs)
}
#[debug_handler]
#[utoipa::path(get, path = "/", description="Get all patients", responses((status = OK, body = Patient)))]
async fn get_all(State(state): State<AppState>) -> Json<Vec<Patient>> {
let patients = query_file_as!(Patient, "queries/get_all.sql")
.fetch_all(&state.pool)
.await
.expect("Could not fetch patients");
Json(patients)
}
#[debug_handler]
#[utoipa::path(get, path = "/{id}", description="Get all patients", responses((status = OK, body = Patient)))]
async fn get_patient_by_id(
State(state): State<AppState>,
Path(path): Path<PathParams>,
) -> impl IntoResponse {
let patient = query_file_as!(Patient, "queries/get_patient_by_id.sql", path.id)
.fetch_optional(&state.pool)
.await
.expect("Could not fetch patients");
match patient {
Some(p) => Json(p).into_response(),
None => StatusCode::NOT_FOUND.into_response(),
}
}
#[debug_handler]
#[utoipa::path(post, path = "/", description = "Create a new patient", responses((status = OK)))]
async fn create_patient(
State(state): State<AppState>,
Json(patient): Json<CreatePatient>,
) -> Json<Patient> {
let patient = query_file_as!(
Patient,
"queries/create_patient.sql",
patient.full_name,
patient.phone
)
.fetch_one(&state.pool)
.await
.expect("Could not create new patient");
Json(patient)
}
#[debug_handler]
#[utoipa::path(post, path = "/{id}", description = "Update patient by ID", responses((status = OK)))]
async fn update_patient_by_id(
State(state): State<AppState>,
Path(path): Path<PathParams>,
Json(patient): Json<CreatePatient>,
) -> impl IntoResponse {
query_file!(
"queries/update_patient.sql",
path.id,
patient.full_name,
patient.phone
)
.fetch_one(&state.pool)
.await
.expect("Could not create new patient");
StatusCode::CREATED
}
#[debug_handler]
#[utoipa::path(delete, path = "/{id}", description = "Delete patient by ID", responses((status = OK)))]
async fn delete_patient_by_id(
State(state): State<AppState>,
Path(path): Path<PathParams>,
) -> impl IntoResponse {
query_file_as!(Patient, "queries/delete_patient.sql", path.id)
.fetch_one(&state.pool)
.await
.expect("Could not delete patient");
}

View File

@ -1,3 +1,25 @@
fn main() {
println!("Hello, world!");
use hospital_server::app_state::AppState;
use hospital_server::router;
use sqlx::SqlitePool;
use tracing::info;
#[tokio::main]
async fn main() {
let _ = dotenvy::dotenv();
tracing_subscriber::fmt::init();
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let pool = SqlitePool::connect(&db_url)
.await
.expect("Cannot connect to db");
sqlx::migrate!().run(&pool).await.unwrap();
let state = AppState { pool };
let router = router(state);
let addr = "0.0.0.0:3000";
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
info!("Listening in {:?}", listener.local_addr());
axum::serve(listener, router).await.unwrap();
}

20
src/models.rs Normal file
View File

@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Clone, ToSchema, Serialize, Deserialize)]
pub struct Patient {
pub id: i64,
pub full_name: String,
pub phone: String,
}
#[derive(Debug, Clone, ToSchema, Serialize, Deserialize)]
pub struct CreatePatient {
pub full_name: String,
pub phone: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathParams {
pub id: i64,
}

9
src/openapi.rs Normal file
View File

@ -0,0 +1,9 @@
use axum::{Router, routing::get};
use utoipa::openapi::OpenApi;
use utoipa_scalar::{Scalar, Servable};
pub fn route_docs(router: Router, docs: OpenApi) -> Router {
router
.merge(Scalar::with_url("/", docs.clone()))
.route("/docs/openapi.json", get(docs.to_json().expect("serializing OpenAPI")))
}

8
src/schemas.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Clone, ToSchema, Serialize, Deserialize)]
pub struct Patient {
name: String,
diagnosis: String, //todo! enum
}