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:
Ben Melchior
2025-04-12 21:52:34 +02:00
parent 19833ad384
commit ba78627169
9 changed files with 455 additions and 67 deletions

View File

@@ -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;
// 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;
}
}
if !found {
log_with_timestamp(&format!("Hospital with ID {} not found.", hospital_id));
return Ok(());
}
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 );
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 &current_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> {