2023-08-25 03:42:19 +02:00
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 ;
2023-08-26 06:36:12 +02:00
mod v3 ;
2023-08-25 03:42:19 +02:00
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 ( ) ) ) ;
2023-08-26 06:36:12 +02:00
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 ;
}
2023-08-25 03:42:19 +02:00
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) ]
2023-08-26 06:36:12 +02:00
#[ 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
) )
) ]
2023-08-25 03:42:19 +02:00
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 )
2023-08-26 06:36:12 +02:00
// API v3 Endpoints
. service ( v3 ::full_view_data )
2023-08-25 03:42:19 +02:00
// 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 ( ( ) )
}