2023-08-30 04:16:43 +02:00
#[ macro_use ]
extern crate dotenv_codegen ;
extern crate dotenv ;
use dotenv ::dotenv ;
use tokio ::time ::sleep ;
use std ::{ env , error ::Error , fs , net ::Ipv4Addr , net ::Ipv6Addr , thread , time ::Duration , sync ::{ Mutex , Arc , atomic ::{ AtomicBool , Ordering } } } ;
2023-08-25 03:42:19 +02:00
use chrono ::Local ;
use actix_web ::{ middleware ::Logger , web , App , HttpServer , Result } ;
use serde ::{ Deserialize , Serialize } ;
use sqlx ::{ PgPool , Pool , Postgres , Connection } ;
2023-09-02 20:23:34 +02:00
use utoipa ::{ OpenApi , openapi ::security ::{ SecurityScheme , ApiKey , ApiKeyValue } , Modify } ;
2023-08-25 03:42:19 +02:00
use utoipa_swagger_ui ::{ Config , SwaggerUi , Url } ;
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 {
auth : AuthenticationTokens ,
}
#[ 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 {
2023-08-30 04:16:43 +02:00
Ok ( _ ) = > { println! ( " Pinged DB Server at {} " , Local ::now ( ) . format ( " %H:%M:%S " ) ) } ,
Err ( _ ) = > { println! ( " Error pinging Server " ) ; break ; } ,
2023-08-25 03:42:19 +02:00
} ;
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
2023-08-30 04:16:43 +02:00
sleep ( Duration ::from_secs ( 15 ) - passed ) . await ;
2023-08-25 03:42:19 +02:00
}
is_alive . store ( false , Ordering ::Relaxed ) ;
}
#[ actix_web::main ]
async fn main ( ) -> Result < ( ) > {
env_logger ::init ( ) ;
2023-08-30 04:16:43 +02:00
dotenv ( ) . ok ( ) ;
2023-08-25 03:42:19 +02:00
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 " ) ;
2023-08-30 04:16:43 +02:00
println! ( " DATABASE_URL: {} " , env ::var ( " DATABASE_URL " ) . unwrap ( ) ) ; // DBEUG
2023-09-02 20:23:34 +02:00
struct ApiSecurity ;
impl Modify for ApiSecurity {
fn modify ( & self , openapi : & mut utoipa ::openapi ::OpenApi ) {
let components = openapi . components . as_mut ( ) . unwrap ( ) ; // we can unwrap safely since there already is components registered.
components . add_security_scheme (
" api_key " ,
SecurityScheme ::ApiKey ( ApiKey ::Header ( ApiKeyValue ::new ( " x-api-key " ) ) ) ,
) ;
}
}
2023-08-25 03:42:19 +02:00
#[ 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 ,
2023-08-30 04:16:43 +02:00
v3 ::auth ,
v3 ::list_games ,
v3 ::get_game_data ,
v3 ::create_game ,
2023-09-02 20:23:34 +02:00
v3 ::edit_game ,
v3 ::delete_game ,
v3 ::create_group ,
v3 ::edit_group ,
v3 ::delete_group
2023-08-26 06:36:12 +02:00
) ,
components ( schemas (
2023-08-30 04:16:43 +02:00
v3 ::schemas ::AuthReturn ,
v3 ::schemas ::GetGameParam ,
v3 ::schemas ::PostGameParams ,
2023-09-02 20:23:34 +02:00
v3 ::schemas ::UpdateGameParams ,
2023-08-30 04:16:43 +02:00
v3 ::schemas ::DeleteGameParam ,
2023-09-02 20:23:34 +02:00
v3 ::schemas ::PostGroupParams ,
v3 ::schemas ::UpdateGroupParams ,
v3 ::schemas ::DeleteGroupParams ,
2023-08-26 06:36:12 +02:00
v3 ::schemas ::FullViewData ,
v3 ::schemas ::Ethic ,
v3 ::schemas ::EmpireEthic ,
2023-09-02 20:23:34 +02:00
v3 ::schemas ::ChellarisGameLegacy ,
v3 ::schemas ::ChellarisGameFlat ,
2023-08-26 06:36:12 +02:00
v3 ::schemas ::ChellarisGame ,
v3 ::schemas ::Species ,
2023-09-02 20:23:34 +02:00
v3 ::schemas ::ChellarisGameGroupLegacy ,
v3 ::schemas ::ChellarisGroupFlat ,
2023-08-26 06:36:12 +02:00
v3 ::schemas ::Portrait ,
2023-09-02 20:23:34 +02:00
v3 ::schemas ::ChellarisEmpire ,
v3 ::schemas ::ChellarisEmpireFlat
) ) ,
modifiers ( & ApiSecurity )
2023-08-26 06:36:12 +02:00
) ]
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 ( ) ;
2023-08-30 04:16:43 +02:00
let pool = PgPool ::connect ( dotenv! ( " DATABASE_URL " ) ) . await . unwrap ( ) ;
2023-08-25 03:42:19 +02:00
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-30 04:16:43 +02:00
. service ( v3 ::auth )
. service ( v3 ::list_games )
. service ( v3 ::get_game_data )
. service ( v3 ::create_game )
2023-09-02 20:23:34 +02:00
. service ( v3 ::edit_game )
2023-08-30 04:16:43 +02:00
. service ( v3 ::delete_game )
2023-09-02 20:23:34 +02:00
. service ( v3 ::create_group )
. service ( v3 ::edit_group )
. service ( v3 ::delete_group )
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 {
2023-08-30 04:16:43 +02:00
println! ( " Awaiting server " ) ;
2023-08-25 03:42:19 +02:00
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 {
2023-09-02 20:23:34 +02:00
sleep ( Duration ::from_millis ( 100 ) ) ;
2023-08-25 03:42:19 +02:00
} ) ;
let _ = thread . await ;
}
if shutdown . load ( Ordering ::Relaxed ) {
break ;
}
eprintln! ( " Connection died, restarting Server " ) ;
server_thread . abort ( ) ;
}
Ok ( ( ) )
}