first commit
This commit is contained in:
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
||||
# SERVER
|
||||
HOST=127.0.0.1
|
||||
PORT=3000
|
||||
|
||||
# DB
|
||||
DATABASE_URL=postgres://hospitalapi:NVtqiNsQq2t7Lss@kn0x.tech/hospitalapi
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Cargo.lock
|
||||
/target
|
||||
.DS_Store
|
||||
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "hospitalapi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.7"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
dotenvy = "0.15.7"
|
||||
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres"] }
|
||||
chrono = "0.4.38"
|
||||
chrono-tz = "0.10.0"
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## Routes
|
||||
|
||||
# - / & /hospital
|
||||
|
||||
List al Hospitals from Database.
|
||||
16
hospital.json
Normal file
16
hospital.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hospitals": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Centre Hospitalier",
|
||||
"address": "4, rue Ernest Barblé L-1210 Luxembourg",
|
||||
"phone": "+352 44 11 11"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Hopital Kirchberg",
|
||||
"address": "9, rue Edward Steichen L-2540 Luxembourg",
|
||||
"phone": "+352 24 68-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
131
src/db/init.rs
Normal file
131
src/db/init.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use sqlx::PgPool;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct HospitalData {
|
||||
hospitals: Vec<Hospital>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Hospital {
|
||||
id: i32,
|
||||
name: String,
|
||||
address: String,
|
||||
phone: String,
|
||||
}
|
||||
|
||||
pub async fn initiate_database(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
// Drop the hospitals table for testing
|
||||
sqlx::query("DROP TABLE IF EXISTS shifts;")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP TABLE IF EXISTS hospitals;")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if !table_exists(pool).await? {
|
||||
println!("Initializing database...");
|
||||
|
||||
println!("Creating hospitals table...");
|
||||
create_hospital_table(pool).await?;
|
||||
|
||||
println!("Creating shifts table...");
|
||||
create_shifts_table(pool).await?;
|
||||
|
||||
println!("Database initialization completed successfully");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn table_exists(pool: &PgPool) -> Result<bool, sqlx::Error> {
|
||||
let exists = sqlx::query_scalar::<_, bool>(
|
||||
r#"
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'hospitals'
|
||||
)
|
||||
"#
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
// Create the hospitals table if it doesn't exist
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS hospitals (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// Check if hospitals table is empty before inserting
|
||||
let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM hospitals")
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
// Only insert default hospitals if the table is empty
|
||||
if count == 0 {
|
||||
// Read and parse the hospital.json file
|
||||
let data = fs::read_to_string("hospital.json")
|
||||
.expect("Unable to read hospital.json");
|
||||
let hospital_data: HospitalData = serde_json::from_str(&data)
|
||||
.expect("Unable to parse hospital.json");
|
||||
|
||||
// Insert each hospital from the JSON file
|
||||
for hospital in hospital_data.hospitals {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO hospitals (id, name, address, phone)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
)
|
||||
.bind(hospital.id)
|
||||
.bind(hospital.name)
|
||||
.bind(hospital.address)
|
||||
.bind(hospital.phone)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_shifts_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
// Create the shifts table if it doesn't exist
|
||||
sqlx::query("CREATE EXTENSION IF NOT EXISTS btree_gist")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS shifts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
hospital_id INTEGER NOT NULL REFERENCES hospitals(id),
|
||||
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT no_overlapping_shifts EXCLUDE USING gist (
|
||||
tstzrange(start_time, end_time) WITH &&
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
27
src/db/mod.rs
Normal file
27
src/db/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::PgPool;
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
mod init;
|
||||
pub use init::initiate_database;
|
||||
|
||||
// initialize and return an Arc wrapped PgPool instance
|
||||
pub async fn create_pool() -> Arc<PgPool> {
|
||||
// load environments variables from `.env` file
|
||||
let database_url: String = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
|
||||
// create postgres connection pool
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to create connection pool");
|
||||
|
||||
// Initialize database tables
|
||||
if let Err(e) = initiate_database(&pool).await {
|
||||
eprintln!("Error initializing database: {}", e);
|
||||
panic!("Database initialization failed");
|
||||
}
|
||||
|
||||
Arc::new(pool)
|
||||
}
|
||||
18
src/handlers/root.rs
Normal file
18
src/handlers/root.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use axum::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ApiInfo {
|
||||
name: &'static str,
|
||||
version: &'static str,
|
||||
description: &'static str,
|
||||
}
|
||||
|
||||
pub async fn root_handler() -> Json<ApiInfo> {
|
||||
let info: ApiInfo = ApiInfo {
|
||||
name: "HospitalAPI",
|
||||
version: "0.1",
|
||||
description: "This API provides you with the current hospital on duty in luxembourg city. Call the /getHospital endpoint.",
|
||||
};
|
||||
Json(info)
|
||||
}
|
||||
45
src/main.rs
Normal file
45
src/main.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::{env, net::SocketAddrV4};
|
||||
use dotenvy::dotenv;
|
||||
use utilities::hospital;
|
||||
|
||||
mod db;
|
||||
mod routes;
|
||||
mod handlers {
|
||||
pub mod root;
|
||||
}
|
||||
mod utilities {
|
||||
pub mod hospital;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
// load environment variables from `.env` file
|
||||
dotenv().ok();
|
||||
|
||||
// get environment variables, with default if not set
|
||||
let host: String = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||
let port: String = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
||||
|
||||
// parse the addr
|
||||
let addr: SocketAddrV4 = format!("{}:{}", host, port).parse::<SocketAddrV4>().expect("Invalid address");
|
||||
|
||||
// initialize the database pool connection
|
||||
let pool: std::sync::Arc<sqlx::Pool<sqlx::Postgres>> = db::create_pool().await;
|
||||
|
||||
// check if the programm was called with any arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 && args[1] == "create-hospital-list" {
|
||||
hospital::create_hospital_list();
|
||||
return;
|
||||
}
|
||||
|
||||
// set up the application routes
|
||||
let app: axum::Router = routes::create_router(pool);
|
||||
println!("Server is running on https://{addr}");
|
||||
|
||||
// bind and serve the application
|
||||
let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
}
|
||||
11
src/routes.rs
Normal file
11
src/routes.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use axum::{Router, routing::get};
|
||||
use std::sync::Arc;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::handlers::root::root_handler;
|
||||
|
||||
pub fn create_router(pool: Arc<PgPool>) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(root_handler))
|
||||
.with_state(pool)
|
||||
}
|
||||
177
src/utilities/hospital.rs
Normal file
177
src/utilities/hospital.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use core::panic;
|
||||
use std::io;
|
||||
use chrono::{offset::LocalResult, DateTime, Datelike, Duration, Local, TimeZone};
|
||||
|
||||
struct Hospital {
|
||||
id: u32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
// struct Schedule {
|
||||
// from: DateTime<chrono_tz::Tz>,
|
||||
// to: DateTime<chrono_tz::Tz>,
|
||||
// }
|
||||
|
||||
pub fn create_hospital_list() {
|
||||
|
||||
// create the hospitals
|
||||
let hospitals: [Hospital; 2] = [
|
||||
Hospital {
|
||||
id: 1,
|
||||
name: "Centre Hospitalier".to_string(),
|
||||
},
|
||||
Hospital {
|
||||
id: 2,
|
||||
name: "Hopital Kirchberg".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
// let mut start: DateTime<Local> = Local.with_ymd_and_hms(2025, 1, 6, 8, 0, 0).unwrap(); // set date for testing
|
||||
let mut start: DateTime<Local> = Local::now();
|
||||
let mut end: DateTime<Local> = get_end_of_shift(start);
|
||||
|
||||
println!("It is now {} and the current Shift is ending on {}", start.format("%A %d/%m/%Y %H:%M"), end.format("%A %d/%m/%Y %H:%M"));
|
||||
println!("Whitch Hospital is actualy on duty?");
|
||||
|
||||
// print out the hospitals
|
||||
for hospital in &hospitals {
|
||||
println!("{}) {}", hospital.id, hospital.name)
|
||||
}
|
||||
|
||||
let mut hospital_id_input = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut hospital_id_input)
|
||||
.expect("Failed to read the line");
|
||||
let hospital_id_input = hospital_id_input.trim();
|
||||
let hospital_id: i32 = match hospital_id_input.parse() {
|
||||
Ok(num) => num,
|
||||
Err(_) => {
|
||||
println!("Please enter a valid number!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut hospital_index: usize = (hospital_id - 1) as usize;
|
||||
|
||||
for _ in 1..=100 {
|
||||
|
||||
// Print the current hospital on duty
|
||||
println!("From {} to {} is {} on duty.", start.format("%A %d/%m/%Y %H:%M"), end.format("%A %d/%m/%Y %H:%M"), hospitals[hospital_index].name );
|
||||
|
||||
// new shift
|
||||
start = end;
|
||||
end = get_end_of_shift(start);
|
||||
|
||||
// change hospital
|
||||
hospital_index = if hospital_index == 0 { 1 } else { 0 }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn get_end_of_shift(start: DateTime<Local>) -> DateTime<Local> {
|
||||
|
||||
let end: DateTime<Local>;
|
||||
|
||||
// define weekday & hour
|
||||
let weekday_str: String = start.format("%u").to_string();
|
||||
let weekday: u32 = weekday_str.parse().unwrap();
|
||||
let hour_str: String = start.format("%H").to_string();
|
||||
let hour: u32 = hour_str.parse().unwrap();
|
||||
|
||||
// From Monday to Thursday before 7:00
|
||||
if weekday >= 1 && weekday <= 4 && hour < 7 {
|
||||
|
||||
let next_day: DateTime<Local> = start;
|
||||
let year: i32 = next_day.year();
|
||||
let month: u32 = next_day.month();
|
||||
let day: u32 = next_day.day();
|
||||
|
||||
let local_result: LocalResult<DateTime<Local>> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0);
|
||||
end = match local_result {
|
||||
LocalResult::None => {
|
||||
panic!("The specified date and time do not exist.");
|
||||
},
|
||||
LocalResult::Single(datetime) => datetime,
|
||||
LocalResult::Ambiguous(datetime1, _datetime2, ) => {
|
||||
datetime1
|
||||
}
|
||||
};
|
||||
|
||||
// From Monday to Wednesday after 7:00
|
||||
} else if weekday >= 1 && weekday <= 3 && hour >= 7 {
|
||||
|
||||
let next_day: DateTime<Local> = start + Duration::days(1);
|
||||
let year: i32 = next_day.year();
|
||||
let month: u32 = next_day.month();
|
||||
let day: u32 = next_day.day();
|
||||
|
||||
let local_result: LocalResult<DateTime<Local>> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0);
|
||||
end = match local_result {
|
||||
LocalResult::None => {
|
||||
panic!("The specified date and time do not exist.");
|
||||
},
|
||||
LocalResult::Single(datetime) => datetime,
|
||||
LocalResult::Ambiguous(datetime1, _datetime2, ) => {
|
||||
datetime1
|
||||
}
|
||||
};
|
||||
|
||||
} else if weekday == 4 && hour >= 7 {
|
||||
|
||||
let next_day: DateTime<Local> = start + Duration::days(1);
|
||||
let year: i32 = next_day.year();
|
||||
let month: u32 = next_day.month();
|
||||
let day: u32 = next_day.day();
|
||||
|
||||
let local_result: LocalResult<DateTime<Local>> = Local.with_ymd_and_hms(year, month, day, 17, 0, 0);
|
||||
end = match local_result {
|
||||
LocalResult::None => {
|
||||
panic!("The specified date and time do not exist.");
|
||||
},
|
||||
LocalResult::Single(datetime) => datetime,
|
||||
LocalResult::Ambiguous(datetime1, _datetime2, ) => {
|
||||
datetime1
|
||||
}
|
||||
};
|
||||
|
||||
} else if weekday == 5 && hour < 17 {
|
||||
|
||||
let next_day: DateTime<Local> = start;
|
||||
let year: i32 = next_day.year();
|
||||
let month: u32 = next_day.month();
|
||||
let day: u32 = next_day.day();
|
||||
|
||||
let local_result: LocalResult<DateTime<Local>> = Local.with_ymd_and_hms(year, month, day, 17, 0, 0);
|
||||
end = match local_result {
|
||||
LocalResult::None => {
|
||||
panic!("The specified date and time do not exist.");
|
||||
},
|
||||
LocalResult::Single(datetime) => datetime,
|
||||
LocalResult::Ambiguous(datetime1, _datetime2, ) => {
|
||||
datetime1
|
||||
}
|
||||
};
|
||||
|
||||
// Friday after 17:00
|
||||
} else {
|
||||
|
||||
let count_days: i64 = 8 - weekday as i64;
|
||||
let next_day: DateTime<Local> = start + Duration::days(count_days);
|
||||
let year: i32 = next_day.year();
|
||||
let month: u32 = next_day.month();
|
||||
let day: u32 = next_day.day();
|
||||
|
||||
let local_result: LocalResult<DateTime<Local>> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0);
|
||||
end = match local_result {
|
||||
LocalResult::None => {
|
||||
panic!("The specified date and time do not exist.");
|
||||
},
|
||||
LocalResult::Single(datetime) => datetime,
|
||||
LocalResult::Ambiguous(datetime1, _datetime2, ) => {
|
||||
datetime1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
end
|
||||
}
|
||||
Reference in New Issue
Block a user