212 lines
6.1 KiB
Rust
212 lines
6.1 KiB
Rust
|
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;
|
||
|
|
||
|
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(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(), components(schemas()))]
|
||
|
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)
|
||
|
// 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(())
|
||
|
}
|