Enhance application configuration and functionality
- Updated `.env` file to include `DB_MAX_CONNECTIONS` and `ENVIRONMENT` variables. - Modified `Cargo.toml` to add `thiserror` and `time` dependencies, and updated `sqlx` features. - Refactored `main.rs` to implement periodic tasks for checking future shifts and printing the current hospital. - Expanded routing in `routes.rs` to include a new endpoint for current hospital status. - Improved database initialization in `init.rs` to conditionally drop tables based on the environment. - Enhanced error handling in database operations and added logging for better traceability. - Updated hospital management logic in `hospital.rs` to fetch and store shifts with improved timestamp logging.
This commit is contained in:
3
.env
3
.env
@@ -4,3 +4,6 @@ PORT=3000
|
|||||||
|
|
||||||
# DB
|
# DB
|
||||||
DATABASE_URL=postgres://hospitalapi:NVtqiNsQq2t7Lss@kn0x.tech/hospitalapi
|
DATABASE_URL=postgres://hospitalapi:NVtqiNsQq2t7Lss@kn0x.tech/hospitalapi
|
||||||
|
DB_MAX_CONNECTIONS=5
|
||||||
|
|
||||||
|
ENVIRONMENT=production
|
||||||
@@ -9,6 +9,8 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres"] }
|
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "time"] }
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
chrono-tz = "0.10.0"
|
chrono-tz = "0.10.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
time = "0.3"
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::{fs, env};
|
||||||
|
use thiserror::Error;
|
||||||
|
use crate::utilities::hospital;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum InitError {
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
Database(#[from] sqlx::Error),
|
||||||
|
#[error("File error: {0}")]
|
||||||
|
File(#[from] std::io::Error),
|
||||||
|
#[error("JSON parsing error: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct HospitalData {
|
struct HospitalData {
|
||||||
@@ -15,8 +27,9 @@ struct Hospital {
|
|||||||
phone: String,
|
phone: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initiate_database(pool: &PgPool) -> Result<(), sqlx::Error> {
|
pub async fn initiate_database(pool: &PgPool) -> Result<(), InitError> {
|
||||||
// Drop the hospitals table for testing
|
// Only drop tables in development environment
|
||||||
|
if env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()) == "development" {
|
||||||
sqlx::query("DROP TABLE IF EXISTS shifts;")
|
sqlx::query("DROP TABLE IF EXISTS shifts;")
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -24,6 +37,7 @@ pub async fn initiate_database(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
sqlx::query("DROP TABLE IF EXISTS hospitals;")
|
sqlx::query("DROP TABLE IF EXISTS hospitals;")
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
if !table_exists(pool).await? {
|
if !table_exists(pool).await? {
|
||||||
println!("Initializing database...");
|
println!("Initializing database...");
|
||||||
@@ -35,6 +49,10 @@ pub async fn initiate_database(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
create_shifts_table(pool).await?;
|
create_shifts_table(pool).await?;
|
||||||
|
|
||||||
println!("Database initialization completed successfully");
|
println!("Database initialization completed successfully");
|
||||||
|
|
||||||
|
println!("Generating hospital schedule...");
|
||||||
|
let pool_arc = std::sync::Arc::new(pool.clone());
|
||||||
|
hospital::create_hospital_list(pool_arc).await.map_err(|e| InitError::Database(e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -56,7 +74,7 @@ async fn table_exists(pool: &PgPool) -> Result<bool, sqlx::Error> {
|
|||||||
Ok(exists)
|
Ok(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
async fn create_hospital_table(pool: &PgPool) -> Result<(), InitError> {
|
||||||
// Create the hospitals table if it doesn't exist
|
// Create the hospitals table if it doesn't exist
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
@@ -64,7 +82,9 @@ async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
address VARCHAR(255) NOT NULL,
|
address VARCHAR(255) NOT NULL,
|
||||||
phone VARCHAR(20) NOT NULL
|
phone VARCHAR(20) NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@@ -79,10 +99,8 @@ async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
// Only insert default hospitals if the table is empty
|
// Only insert default hospitals if the table is empty
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
// Read and parse the hospital.json file
|
// Read and parse the hospital.json file
|
||||||
let data = fs::read_to_string("hospital.json")
|
let data = fs::read_to_string("hospital.json")?;
|
||||||
.expect("Unable to read hospital.json");
|
let hospital_data: HospitalData = serde_json::from_str(&data)?;
|
||||||
let hospital_data: HospitalData = serde_json::from_str(&data)
|
|
||||||
.expect("Unable to parse hospital.json");
|
|
||||||
|
|
||||||
// Insert each hospital from the JSON file
|
// Insert each hospital from the JSON file
|
||||||
for hospital in hospital_data.hospitals {
|
for hospital in hospital_data.hospitals {
|
||||||
@@ -104,7 +122,7 @@ async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_shifts_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
async fn create_shifts_table(pool: &PgPool) -> Result<(), InitError> {
|
||||||
// Create the shifts table if it doesn't exist
|
// Create the shifts table if it doesn't exist
|
||||||
sqlx::query("CREATE EXTENSION IF NOT EXISTS btree_gist")
|
sqlx::query("CREATE EXTENSION IF NOT EXISTS btree_gist")
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
@@ -117,7 +135,6 @@ async fn create_shifts_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
|||||||
hospital_id INTEGER NOT NULL REFERENCES hospitals(id),
|
hospital_id INTEGER NOT NULL REFERENCES hospitals(id),
|
||||||
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
end_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 (
|
CONSTRAINT no_overlapping_shifts EXCLUDE USING gist (
|
||||||
tstzrange(start_time, end_time) WITH &&
|
tstzrange(start_time, end_time) WITH &&
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
mod init;
|
mod init;
|
||||||
pub use init::initiate_database;
|
pub use init::initiate_database;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DatabaseError {
|
||||||
|
#[error("Database URL not found in environment")]
|
||||||
|
MissingDatabaseUrl,
|
||||||
|
#[error("Failed to create connection pool: {0}")]
|
||||||
|
PoolCreationError(#[from] sqlx::Error),
|
||||||
|
#[error("Database initialization failed: {0}")]
|
||||||
|
InitializationError(String),
|
||||||
|
}
|
||||||
|
|
||||||
// initialize and return an Arc wrapped PgPool instance
|
// initialize and return an Arc wrapped PgPool instance
|
||||||
pub async fn create_pool() -> Arc<PgPool> {
|
pub async fn create_pool() -> Result<Arc<PgPool>, DatabaseError> {
|
||||||
// load environments variables from `.env` file
|
// load environments variables from `.env` file
|
||||||
let database_url: String = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let database_url = env::var("DATABASE_URL").map_err(|_| DatabaseError::MissingDatabaseUrl)?;
|
||||||
|
|
||||||
|
// Get configuration from environment or use defaults
|
||||||
|
let max_connections = env::var("DB_MAX_CONNECTIONS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(5);
|
||||||
|
|
||||||
// create postgres connection pool
|
// create postgres connection pool
|
||||||
let pool = PgPoolOptions::new()
|
let pool = PgPoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(max_connections)
|
||||||
.connect(&database_url)
|
.connect(&database_url)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create connection pool");
|
.map_err(DatabaseError::PoolCreationError)?;
|
||||||
|
|
||||||
// Initialize database tables
|
// Initialize database tables
|
||||||
if let Err(e) = initiate_database(&pool).await {
|
initiate_database(&pool)
|
||||||
eprintln!("Error initializing database: {}", e);
|
.await
|
||||||
panic!("Database initialization failed");
|
.map_err(|e| DatabaseError::InitializationError(e.to_string()))?;
|
||||||
}
|
|
||||||
|
|
||||||
Arc::new(pool)
|
Ok(Arc::new(pool))
|
||||||
}
|
}
|
||||||
79
src/handlers/current_hospital.rs
Normal file
79
src/handlers/current_hospital.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use chrono::Local;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CurrentHospital {
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
start_time: String,
|
||||||
|
end_time: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct HospitalResponse {
|
||||||
|
success: bool,
|
||||||
|
hospital: Option<CurrentHospital>,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn current_hospital_handler(
|
||||||
|
State(pool): State<Arc<PgPool>>,
|
||||||
|
) -> Json<HospitalResponse> {
|
||||||
|
// Get the current time
|
||||||
|
let now = Local::now();
|
||||||
|
|
||||||
|
// Convert chrono DateTime to time OffsetDateTime for SQLx
|
||||||
|
let now_offset = OffsetDateTime::from_unix_timestamp(now.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
|
||||||
|
// Get the current hospital on duty
|
||||||
|
let result = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT h.id, h.name, s.start_time, s.end_time
|
||||||
|
FROM hospitals h
|
||||||
|
JOIN shifts s ON h.id = s.hospital_id
|
||||||
|
WHERE s.start_time <= $1 AND s.end_time > $1
|
||||||
|
ORDER BY s.end_time ASC
|
||||||
|
LIMIT 1
|
||||||
|
"#,
|
||||||
|
now_offset
|
||||||
|
)
|
||||||
|
.fetch_optional(&*pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Some(hospital)) => {
|
||||||
|
Json(HospitalResponse {
|
||||||
|
success: true,
|
||||||
|
hospital: Some(CurrentHospital {
|
||||||
|
id: hospital.id,
|
||||||
|
name: hospital.name,
|
||||||
|
start_time: hospital.start_time.to_string(),
|
||||||
|
end_time: hospital.end_time.to_string(),
|
||||||
|
}),
|
||||||
|
message: "Current hospital retrieved successfully".to_string(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
Json(HospitalResponse {
|
||||||
|
success: false,
|
||||||
|
hospital: None,
|
||||||
|
message: "No hospital currently on duty".to_string(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Json(HospitalResponse {
|
||||||
|
success: false,
|
||||||
|
hospital: None,
|
||||||
|
message: format!("Error retrieving current hospital: {}", e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/handlers/mod.rs
Normal file
2
src/handlers/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod root;
|
||||||
|
pub mod current_hospital;
|
||||||
52
src/main.rs
52
src/main.rs
@@ -1,19 +1,24 @@
|
|||||||
use std::{env, net::SocketAddrV4};
|
use std::{env, net::SocketAddrV4, time::Duration as StdDuration};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use utilities::hospital;
|
use utilities::hospital;
|
||||||
|
use tokio::time;
|
||||||
|
use chrono::Local;
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod handlers {
|
mod handlers;
|
||||||
pub mod root;
|
|
||||||
}
|
|
||||||
mod utilities {
|
mod utilities {
|
||||||
pub mod hospital;
|
pub mod hospital;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to print with timestamp
|
||||||
|
fn log_with_timestamp(message: &str) {
|
||||||
|
let now = Local::now();
|
||||||
|
println!("[{}] {}", now.format("%Y-%m-%d %H:%M:%S"), message);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
||||||
// load environment variables from `.env` file
|
// load environment variables from `.env` file
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
@@ -25,21 +30,42 @@ async fn main() {
|
|||||||
let addr: SocketAddrV4 = format!("{}:{}", host, port).parse::<SocketAddrV4>().expect("Invalid address");
|
let addr: SocketAddrV4 = format!("{}:{}", host, port).parse::<SocketAddrV4>().expect("Invalid address");
|
||||||
|
|
||||||
// initialize the database pool connection
|
// initialize the database pool connection
|
||||||
let pool: std::sync::Arc<sqlx::Pool<sqlx::Postgres>> = db::create_pool().await;
|
let pool: std::sync::Arc<sqlx::Pool<sqlx::Postgres>> = db::create_pool()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create database pool");
|
||||||
|
|
||||||
// check if the programm was called with any arguments
|
// Set up a periodic task to check for future shifts every 48 hours
|
||||||
let args: Vec<String> = env::args().collect();
|
let pool_clone = pool.clone();
|
||||||
if args.len() > 1 && args[1] == "create-hospital-list" {
|
tokio::spawn(async move {
|
||||||
hospital::create_hospital_list();
|
let mut interval = time::interval(StdDuration::from_secs(48 * 60 * 60)); // 48 hours
|
||||||
return;
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
log_with_timestamp("Periodic check for future shifts...");
|
||||||
|
if let Err(e) = hospital::check_and_create_future_shifts(pool_clone.clone()).await {
|
||||||
|
eprintln!("[{}] Error checking for future shifts: {}",
|
||||||
|
Local::now().format("%Y-%m-%d %H:%M:%S"), e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up a task to print the current hospital every minute
|
||||||
|
let pool_clone = pool.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut interval = time::interval(StdDuration::from_secs(60)); // 1 minute
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
if let Err(e) = hospital::print_current_hospital(pool_clone.clone()).await {
|
||||||
|
eprintln!("[{}] Error printing current hospital: {}",
|
||||||
|
Local::now().format("%Y-%m-%d %H:%M:%S"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// set up the application routes
|
// set up the application routes
|
||||||
let app: axum::Router = routes::create_router(pool);
|
let app: axum::Router = routes::create_router(pool);
|
||||||
println!("Server is running on https://{addr}");
|
log_with_timestamp(&format!("Server is running on https://{}", addr));
|
||||||
|
|
||||||
// bind and serve the application
|
// bind and serve the application
|
||||||
let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,11 @@ use std::sync::Arc;
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::handlers::root::root_handler;
|
use crate::handlers::root::root_handler;
|
||||||
|
use crate::handlers::current_hospital::current_hospital_handler;
|
||||||
|
|
||||||
pub fn create_router(pool: Arc<PgPool>) -> Router {
|
pub fn create_router(pool: Arc<PgPool>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(root_handler))
|
.route("/", get(root_handler))
|
||||||
|
.route("/current-hospital", get(current_hospital_handler))
|
||||||
.with_state(pool)
|
.with_state(pool)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
use core::panic;
|
use core::panic;
|
||||||
use std::io;
|
use std::io;
|
||||||
use chrono::{offset::LocalResult, DateTime, Datelike, Duration, Local, TimeZone};
|
use chrono::{offset::LocalResult, DateTime, Datelike, Duration, Local, TimeZone};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Hospital {
|
struct Hospital {
|
||||||
id: u32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,30 +17,43 @@ struct Hospital {
|
|||||||
// to: DateTime<chrono_tz::Tz>,
|
// to: DateTime<chrono_tz::Tz>,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn create_hospital_list() {
|
// Helper function to print with timestamp
|
||||||
|
fn log_with_timestamp(message: &str) {
|
||||||
|
let now = Local::now();
|
||||||
|
println!("[{}] {}", now.format("%Y-%m-%d %H:%M:%S"), message);
|
||||||
|
}
|
||||||
|
|
||||||
// create the hospitals
|
// Static variable to store the last printed hospital ID
|
||||||
let hospitals: [Hospital; 2] = [
|
static LAST_HOSPITAL_ID: Mutex<Option<i32>> = Mutex::new(None);
|
||||||
Hospital {
|
|
||||||
id: 1,
|
pub async fn create_hospital_list(pool: Arc<PgPool>) -> Result<(), sqlx::Error> {
|
||||||
name: "Centre Hospitalier".to_string(),
|
// Fetch hospitals from the database
|
||||||
},
|
let hospitals = sqlx::query_as!(
|
||||||
Hospital {
|
Hospital,
|
||||||
id: 2,
|
r#"
|
||||||
name: "Hopital Kirchberg".to_string(),
|
SELECT id, name FROM hospitals ORDER BY id
|
||||||
},
|
"#
|
||||||
];
|
)
|
||||||
|
.fetch_all(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if hospitals.is_empty() {
|
||||||
|
log_with_timestamp("No hospitals found in the database.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// 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.with_ymd_and_hms(2025, 1, 6, 8, 0, 0).unwrap(); // set date for testing
|
||||||
let mut start: DateTime<Local> = Local::now();
|
let mut start: DateTime<Local> = Local::now();
|
||||||
let mut end: DateTime<Local> = get_end_of_shift(start);
|
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"));
|
log_with_timestamp(&format!("It is now {} and the current Shift is ending on {}",
|
||||||
println!("Whitch Hospital is actualy on duty?");
|
start.format("%A %d/%m/%Y %H:%M"),
|
||||||
|
end.format("%A %d/%m/%Y %H:%M")));
|
||||||
|
log_with_timestamp("Which Hospital is currently on duty?");
|
||||||
|
|
||||||
// print out the hospitals
|
// print out the hospitals
|
||||||
for hospital in &hospitals {
|
for hospital in &hospitals {
|
||||||
println!("{}) {}", hospital.id, hospital.name)
|
log_with_timestamp(&format!("{}) {}", hospital.id, hospital.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hospital_id_input = String::new();
|
let mut hospital_id_input = String::new();
|
||||||
@@ -46,26 +64,249 @@ pub fn create_hospital_list() {
|
|||||||
let hospital_id: i32 = match hospital_id_input.parse() {
|
let hospital_id: i32 = match hospital_id_input.parse() {
|
||||||
Ok(num) => num,
|
Ok(num) => num,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
println!("Please enter a valid number!");
|
log_with_timestamp("Please enter a valid number!");
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut hospital_index: usize = (hospital_id - 1) as usize;
|
|
||||||
|
|
||||||
for _ in 1..=100 {
|
// Find the index of the selected hospital
|
||||||
|
let mut hospital_index: usize = 0;
|
||||||
|
let mut found = false;
|
||||||
|
for (i, hospital) in hospitals.iter().enumerate() {
|
||||||
|
if hospital.id == hospital_id {
|
||||||
|
hospital_index = i;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Print the current hospital on duty
|
if !found {
|
||||||
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 );
|
log_with_timestamp(&format!("Hospital with ID {} not found.", hospital_id));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
log_with_timestamp("Generating and storing shifts in the database...");
|
||||||
|
|
||||||
|
// Clear existing shifts
|
||||||
|
sqlx::query("DELETE FROM shifts")
|
||||||
|
.execute(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Generate and store 100 shifts
|
||||||
|
for i in 1..=100 {
|
||||||
|
// Format the timestamps as strings for database storage
|
||||||
|
let start_str = start.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||||
|
let end_str = end.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||||
|
|
||||||
|
// Store the current shift in the database
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO shifts (hospital_id, start_time, end_time)
|
||||||
|
VALUES ($1, $2::timestamp with time zone, $3::timestamp with time zone)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(hospitals[hospital_index].id)
|
||||||
|
.bind(start_str)
|
||||||
|
.bind(end_str)
|
||||||
|
.execute(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Print progress only every 10 shifts
|
||||||
|
if i % 10 == 0 {
|
||||||
|
log_with_timestamp(&format!("Stored {} shifts...", i));
|
||||||
|
}
|
||||||
|
|
||||||
// new shift
|
// new shift
|
||||||
start = end;
|
start = end;
|
||||||
end = get_end_of_shift(start);
|
end = get_end_of_shift(start);
|
||||||
|
|
||||||
// change hospital
|
// change hospital
|
||||||
hospital_index = if hospital_index == 0 { 1 } else { 0 }
|
hospital_index = if hospital_index == 0 { 1 } else { 0 };
|
||||||
|
|
||||||
|
// If we only have one hospital, don't switch
|
||||||
|
if hospitals.len() == 1 {
|
||||||
|
hospital_index = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_with_timestamp("Successfully stored 100 shifts in the database.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_and_create_future_shifts(pool: Arc<PgPool>) -> Result<(), sqlx::Error> {
|
||||||
|
// Get the current time
|
||||||
|
let now = Local::now();
|
||||||
|
|
||||||
|
// Calculate the date two months from now
|
||||||
|
let two_months_from_now = now + Duration::days(60);
|
||||||
|
|
||||||
|
// Convert chrono DateTime to time OffsetDateTime for SQLx
|
||||||
|
let now_offset = OffsetDateTime::from_unix_timestamp(now.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
let two_months_offset = OffsetDateTime::from_unix_timestamp(two_months_from_now.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
|
||||||
|
// Check if there are any shifts in the next two months
|
||||||
|
let future_shifts_count = sqlx::query_scalar::<_, i64>(
|
||||||
|
r#"
|
||||||
|
SELECT COUNT(*) FROM shifts
|
||||||
|
WHERE start_time > $1 AND start_time < $2
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(now_offset)
|
||||||
|
.bind(two_months_offset)
|
||||||
|
.fetch_one(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// If there are no future shifts, create them
|
||||||
|
if future_shifts_count == 0 {
|
||||||
|
log_with_timestamp("No future shifts found. Creating shifts for the next two months...");
|
||||||
|
|
||||||
|
// Get the current hospital on duty
|
||||||
|
let current_hospital = sqlx::query_as!(
|
||||||
|
Hospital,
|
||||||
|
r#"
|
||||||
|
SELECT h.id, h.name
|
||||||
|
FROM hospitals h
|
||||||
|
JOIN shifts s ON h.id = s.hospital_id
|
||||||
|
WHERE s.end_time > $1
|
||||||
|
ORDER BY s.end_time ASC
|
||||||
|
LIMIT 1
|
||||||
|
"#,
|
||||||
|
now_offset
|
||||||
|
)
|
||||||
|
.fetch_optional(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// If no current hospital is found, get the first hospital from the database
|
||||||
|
let hospital_id = match current_hospital {
|
||||||
|
Some(hospital) => hospital.id,
|
||||||
|
None => {
|
||||||
|
let first_hospital = sqlx::query_as!(
|
||||||
|
Hospital,
|
||||||
|
r#"
|
||||||
|
SELECT id, name FROM hospitals ORDER BY id LIMIT 1
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.fetch_optional(&*pool)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| sqlx::Error::RowNotFound)?;
|
||||||
|
|
||||||
|
first_hospital.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the index of the selected hospital
|
||||||
|
let hospitals = sqlx::query_as!(
|
||||||
|
Hospital,
|
||||||
|
r#"
|
||||||
|
SELECT id, name FROM hospitals ORDER BY id
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.fetch_all(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut hospital_index = 0;
|
||||||
|
for (i, hospital) in hospitals.iter().enumerate() {
|
||||||
|
if hospital.id == hospital_id {
|
||||||
|
hospital_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate shifts starting from now
|
||||||
|
let mut start = now;
|
||||||
|
let mut end = get_end_of_shift(start);
|
||||||
|
|
||||||
|
// Generate shifts for the next two months (approximately 60 days)
|
||||||
|
for _ in 1..=120 {
|
||||||
|
// Convert chrono DateTime to time OffsetDateTime for SQLx
|
||||||
|
let start_offset = OffsetDateTime::from_unix_timestamp(start.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
let end_offset = OffsetDateTime::from_unix_timestamp(end.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
|
||||||
|
// Store the current shift in the database
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO shifts (hospital_id, start_time, end_time)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(hospitals[hospital_index].id)
|
||||||
|
.bind(start_offset)
|
||||||
|
.bind(end_offset)
|
||||||
|
.execute(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// new shift
|
||||||
|
start = end;
|
||||||
|
end = get_end_of_shift(start);
|
||||||
|
|
||||||
|
// change hospital
|
||||||
|
hospital_index = if hospital_index == 0 { 1 } else { 0 };
|
||||||
|
|
||||||
|
// If we only have one hospital, don't switch
|
||||||
|
if hospitals.len() == 1 {
|
||||||
|
hospital_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_with_timestamp("Successfully created future shifts for the next two months.");
|
||||||
|
} else {
|
||||||
|
log_with_timestamp(&format!("Found {} future shifts. No need to create more.", future_shifts_count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn print_current_hospital(pool: Arc<PgPool>) -> Result<(), sqlx::Error> {
|
||||||
|
// Get the current time
|
||||||
|
let now = Local::now();
|
||||||
|
|
||||||
|
// Convert chrono DateTime to time OffsetDateTime for SQLx
|
||||||
|
let now_offset = OffsetDateTime::from_unix_timestamp(now.timestamp())
|
||||||
|
.expect("Failed to convert to OffsetDateTime");
|
||||||
|
|
||||||
|
// Get the current hospital on duty
|
||||||
|
let current_hospital = sqlx::query_as!(
|
||||||
|
Hospital,
|
||||||
|
r#"
|
||||||
|
SELECT h.id, h.name
|
||||||
|
FROM hospitals h
|
||||||
|
JOIN shifts s ON h.id = s.hospital_id
|
||||||
|
WHERE s.start_time <= $1 AND s.end_time > $1
|
||||||
|
ORDER BY s.end_time ASC
|
||||||
|
LIMIT 1
|
||||||
|
"#,
|
||||||
|
now_offset
|
||||||
|
)
|
||||||
|
.fetch_optional(&*pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Get the last printed hospital ID
|
||||||
|
let mut last_id = LAST_HOSPITAL_ID.lock().unwrap();
|
||||||
|
|
||||||
|
// Check if the hospital has changed
|
||||||
|
let current_id = current_hospital.as_ref().map(|h| h.id);
|
||||||
|
let has_changed = *last_id != current_id;
|
||||||
|
|
||||||
|
// Only print if the hospital has changed
|
||||||
|
if has_changed {
|
||||||
|
match ¤t_hospital {
|
||||||
|
Some(hospital) => {
|
||||||
|
log_with_timestamp(&format!("Current hospital on duty: {} (ID: {})", hospital.name, hospital.id));
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log_with_timestamp("No hospital currently on duty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the last printed hospital ID
|
||||||
|
*last_id = current_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_end_of_shift(start: DateTime<Local>) -> DateTime<Local> {
|
fn get_end_of_shift(start: DateTime<Local>) -> DateTime<Local> {
|
||||||
|
|||||||
Reference in New Issue
Block a user