use std::{error::Error, fs, net::Ipv4Addr, net::Ipv6Addr, thread, time::Duration, sync::{Mutex, Arc, atomic::{AtomicBool, Ordering}}}; use chrono::Local; use actix_web::{middleware::Logger, web, App, HttpServer, Result}; use serde::{Deserialize, Serialize}; use sqlx::{PgPool, Pool, Postgres, Connection}; use utoipa::OpenApi; use utoipa_swagger_ui::{Config, SwaggerUi, Url}; use crate::db::connect_postgres; mod db; mod v2; mod v3; macro_rules! api_base { () => { "/api" }; } macro_rules! api_base_2 { () => { "/api/v2" }; } macro_rules! api_base_3 { () => { "/api/v3" }; } #[derive(Serialize, Deserialize, Debug, Clone)] pub(crate) struct ConfigToml { database: PostgresConfig, auth: AuthenticationTokens, } #[derive(Serialize, Deserialize, Debug, Clone)] pub(crate) struct PostgresConfig { host: String, port: u16, user: String, password: String, db: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub(crate) struct AuthenticationTokens { moderator: String, admin: String } pub struct AppState { db: Pool<Postgres>, auth_tokens: AuthenticationTokens, } async fn postgres_watchdog(pool: PgPool, is_alive: Arc<AtomicBool>, shutdown: Arc<AtomicBool>) { loop { if shutdown.load(Ordering::Relaxed) { break; } let start = Local::now(); let mut conn = match pool.acquire().await { Ok(data) => data, Err(_) => break, }; match conn.ping().await { Ok(_) => {eprintln!("Pinged DB Server at {}", Local::now().format("%H:%M:%S"))}, Err(_) => todo!(), }; let passed = (Local::now() - start).to_std().expect(&format!("Unable to get Time Difference for '{}' and '{}'", start, Local::now())); thread::sleep(Duration::from_secs(5) - passed); if shutdown.load(Ordering::Relaxed) { break; } thread::sleep(Duration::from_secs(10) - passed); if shutdown.load(Ordering::Relaxed) { break; } thread::sleep(Duration::from_secs(15) - passed); } is_alive.store(false, Ordering::Relaxed); } #[actix_web::main] async fn main() -> Result<()> { env_logger::init(); let shutdown: Arc<AtomicBool> = Arc::new(AtomicBool::new(false)); let shutdown_clone = Arc::clone(&shutdown); ctrlc::set_handler(move || { eprintln!("Ctrl-C received"); shutdown_clone.store(true, Ordering::Relaxed) }) .expect("Error setting Ctrl-C handler"); let toml_str = fs::read_to_string("config.toml").expect("Failed to read config.toml"); let config: ConfigToml = toml::from_str(&toml_str).expect("Failed to parse config.toml"); #[derive(OpenApi)] #[openapi( paths( v2::empire_ethics, v2::empires, v2::ethics, v2::game_groups, v2::games, v2::portrait_groups, v2::portraits ), components(schemas( v2::schemas::PortraitGroup, v2::schemas::Portrait, v2::schemas::Ethic, v2::schemas::Empire, v2::schemas::EmpireEthic, v2::schemas::Game, v2::schemas::GameGroup )) )] struct ApiDocV2; #[derive(OpenApi)] #[openapi( paths( v3::full_view_data, ), components(schemas( v3::schemas::FullViewData, v3::schemas::Ethic, v3::schemas::EmpireEthic, v3::schemas::ChellarisGame, v3::schemas::Species, v3::schemas::ChellarisGameGroup, v3::schemas::Portrait, v3::schemas::ChellarisEmpire )) )] struct ApiDocV3; let openapi_urls = vec![ Url::new("v2", concat!(api_base_2!(), "/openapi.json")), Url::new("v3", concat!(api_base_3!(), "/openapi.json")), ]; let is_alive: Arc<AtomicBool> = Arc::new(AtomicBool::new(true)); loop { let db_auth_tokens = config.auth.clone(); let pool = connect_postgres(config.database.clone()).await.unwrap(); let pool_copy = pool.clone(); let swagger_config = Config::new(openapi_urls.clone()); let mut openapi_v2 = ApiDocV2::openapi(); openapi_v2.info.title = "Chellaris Rust API".to_string(); let mut openapi_v3 = ApiDocV3::openapi(); openapi_v3.info.title = "Chellaris Rust API".to_string(); let server = HttpServer::new(move || { App::new() .app_data(web::Data::new(AppState { db: pool.clone(), auth_tokens: db_auth_tokens.clone() })) .wrap(Logger::default()) // API v2 Endpoints .service(v2::empire_ethics) .service(v2::empires) .service(v2::ethics) .service(v2::game_groups) .service(v2::games) .service(v2::portrait_groups) .service(v2::portraits) // API v3 Endpoints .service(v3::full_view_data) // Swagger UI .service( SwaggerUi::new(concat!(api_base!(), "/swagger/{_:.*}")) .urls(vec![ ( Url::new("v2", concat!(api_base_2!(), "/openapi.json")), openapi_v2.clone(), ), ( Url::new("v3", concat!(api_base_3!(), "/openapi.json")), openapi_v3.clone(), ), ]) .config(swagger_config.clone()), ) }) .bind((Ipv6Addr::UNSPECIFIED, 8080)).expect("Port or IP already occupied") .run(); let server_thread = tokio::spawn(async { eprintln!("Awaiting server"); let _ = server.await; println!("Stopped awaiting server"); }); println!("Started Serving API on: "); println!(" -> http://[{}]:{}", Ipv6Addr::UNSPECIFIED, 8080); println!(" -> http://{}:{}", Ipv4Addr::UNSPECIFIED, 8080); let is_alive_clone = Arc::clone(&is_alive); let shutdown_clone = Arc::clone(&shutdown); let _ = tokio::spawn(async move { postgres_watchdog(pool_copy, is_alive_clone, shutdown_clone).await }); //watchdog_thread.await; while is_alive.load(Ordering::Relaxed) { let thread = tokio::spawn(async { thread::sleep(Duration::from_millis(100)); }); let _ = thread.await; } if shutdown.load(Ordering::Relaxed) { break; } eprintln!("Connection died, restarting Server"); server_thread.abort(); } Ok(()) }