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
|
||||
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_json = "1.0"
|
||||
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-tz = "0.10.0"
|
||||
thiserror = "1.0"
|
||||
time = "0.3"
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
use sqlx::PgPool;
|
||||
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)]
|
||||
struct HospitalData {
|
||||
@@ -15,15 +27,17 @@ struct Hospital {
|
||||
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?;
|
||||
pub async fn initiate_database(pool: &PgPool) -> Result<(), InitError> {
|
||||
// Only drop tables in development environment
|
||||
if env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()) == "development" {
|
||||
sqlx::query("DROP TABLE IF EXISTS shifts;")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP TABLE IF EXISTS hospitals;")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("DROP TABLE IF EXISTS hospitals;")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if !table_exists(pool).await? {
|
||||
println!("Initializing database...");
|
||||
@@ -35,6 +49,10 @@ pub async fn initiate_database(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
create_shifts_table(pool).await?;
|
||||
|
||||
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(())
|
||||
@@ -56,7 +74,7 @@ async fn table_exists(pool: &PgPool) -> Result<bool, sqlx::Error> {
|
||||
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
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -64,7 +82,9 @@ async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
id SERIAL PRIMARY KEY,
|
||||
name 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
|
||||
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");
|
||||
let data = fs::read_to_string("hospital.json")?;
|
||||
let hospital_data: HospitalData = serde_json::from_str(&data)?;
|
||||
|
||||
// Insert each hospital from the JSON file
|
||||
for hospital in hospital_data.hospitals {
|
||||
@@ -104,7 +122,7 @@ async fn create_hospital_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
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
|
||||
sqlx::query("CREATE EXTENSION IF NOT EXISTS btree_gist")
|
||||
.execute(pool)
|
||||
@@ -117,7 +135,6 @@ async fn create_shifts_table(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
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 &&
|
||||
)
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::PgPool;
|
||||
use std::{env, sync::Arc};
|
||||
use thiserror::Error;
|
||||
|
||||
mod init;
|
||||
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
|
||||
pub async fn create_pool() -> Arc<PgPool> {
|
||||
pub async fn create_pool() -> Result<Arc<PgPool>, DatabaseError> {
|
||||
// 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
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.max_connections(max_connections)
|
||||
.connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to create connection pool");
|
||||
.map_err(DatabaseError::PoolCreationError)?;
|
||||
|
||||
// Initialize database tables
|
||||
if let Err(e) = initiate_database(&pool).await {
|
||||
eprintln!("Error initializing database: {}", e);
|
||||
panic!("Database initialization failed");
|
||||
}
|
||||
initiate_database(&pool)
|
||||
.await
|
||||
.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;
|
||||
54
src/main.rs
54
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 utilities::hospital;
|
||||
use tokio::time;
|
||||
use chrono::Local;
|
||||
|
||||
mod db;
|
||||
mod routes;
|
||||
mod handlers {
|
||||
pub mod root;
|
||||
}
|
||||
mod handlers;
|
||||
mod utilities {
|
||||
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]
|
||||
async fn main() {
|
||||
|
||||
// load environment variables from `.env` file
|
||||
dotenv().ok();
|
||||
|
||||
@@ -25,21 +30,42 @@ async fn main() {
|
||||
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;
|
||||
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
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 && args[1] == "create-hospital-list" {
|
||||
hospital::create_hospital_list();
|
||||
return;
|
||||
}
|
||||
// Set up a periodic task to check for future shifts every 48 hours
|
||||
let pool_clone = pool.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = time::interval(StdDuration::from_secs(48 * 60 * 60)); // 48 hours
|
||||
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
|
||||
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
|
||||
let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
}
|
||||
@@ -3,9 +3,11 @@ use std::sync::Arc;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::handlers::root::root_handler;
|
||||
use crate::handlers::current_hospital::current_hospital_handler;
|
||||
|
||||
pub fn create_router(pool: Arc<PgPool>) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(root_handler))
|
||||
.route("/current-hospital", get(current_hospital_handler))
|
||||
.with_state(pool)
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
use core::panic;
|
||||
use std::io;
|
||||
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 {
|
||||
id: u32,
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
@@ -12,30 +17,43 @@ struct Hospital {
|
||||
// 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
|
||||
let hospitals: [Hospital; 2] = [
|
||||
Hospital {
|
||||
id: 1,
|
||||
name: "Centre Hospitalier".to_string(),
|
||||
},
|
||||
Hospital {
|
||||
id: 2,
|
||||
name: "Hopital Kirchberg".to_string(),
|
||||
},
|
||||
];
|
||||
// Static variable to store the last printed hospital ID
|
||||
static LAST_HOSPITAL_ID: Mutex<Option<i32>> = Mutex::new(None);
|
||||
|
||||
pub async fn create_hospital_list(pool: Arc<PgPool>) -> Result<(), sqlx::Error> {
|
||||
// Fetch hospitals from the database
|
||||
let hospitals = sqlx::query_as!(
|
||||
Hospital,
|
||||
r#"
|
||||
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::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?");
|
||||
log_with_timestamp(&format!("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("Which Hospital is currently on duty?");
|
||||
|
||||
// print out the 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();
|
||||
@@ -46,26 +64,249 @@ pub fn create_hospital_list() {
|
||||
let hospital_id: i32 = match hospital_id_input.parse() {
|
||||
Ok(num) => num,
|
||||
Err(_) => {
|
||||
println!("Please enter a valid number!");
|
||||
return;
|
||||
log_with_timestamp("Please enter a valid number!");
|
||||
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
|
||||
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 );
|
||||
if !found {
|
||||
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
|
||||
start = end;
|
||||
end = get_end_of_shift(start);
|
||||
|
||||
// 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> {
|
||||
|
||||
Reference in New Issue
Block a user