Files
hospitalapi/src/utilities/hospital.rs
Ben Melchior ba78627169 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.
2025-04-12 21:52:34 +02:00

418 lines
13 KiB
Rust

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: i32,
name: String,
}
// struct Schedule {
// from: DateTime<chrono_tz::Tz>,
// to: DateTime<chrono_tz::Tz>,
// }
// 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);
}
// 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);
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 {
log_with_timestamp(&format!("{}) {}", 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(_) => {
log_with_timestamp("Please enter a valid number!");
return Ok(());
}
};
// 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(());
}
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 };
// 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> {
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
}