// StarKingdoms.IO, a browser game about drifting through space // Copyright (C) 2023 ghostly_zsh, TerraMaster85, core // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use crate::config::Config; use crate::error::APIErrorResponse; use actix_web::middleware::Logger; use actix_web::web::{Data, JsonConfig}; use actix_web::{App, Error, HttpResponse, HttpServer}; use diesel::Connection; use diesel_async::async_connection_wrapper::AsyncConnectionWrapper; use diesel_async::pooled_connection::bb8::Pool; use diesel_async::pooled_connection::AsyncDieselConnectionManager; use diesel_async::AsyncPgConnection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use hmac::digest::KeyInit; use hmac::Hmac; use log::{error, info}; use sha2::Sha256; use snowflake::SnowflakeIdGenerator; use std::fs; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::{Duration, UNIX_EPOCH}; pub mod error; #[macro_use] pub mod response; pub mod config; pub mod models; pub mod routes; pub mod schema; pub mod auth; pub mod tokens; #[derive(Clone)] pub struct AppState { pub config: Config, pub pool: bb8::Pool>, pub idgen: Arc>, pub key: Hmac, } pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); #[actix_web::main] async fn main() { env_logger::init(); info!( "StarKingdoms API v{} starting up", env!("CARGO_PKG_VERSION") ); let mut args = std::env::args(); let config_path = match args.nth(1) { Some(path) => path, None => { eprintln!("usage: starkingdoms-api "); std::process::exit(1); } }; let config_pathbuf = PathBuf::from(config_path); info!("Loading config from {}", config_pathbuf.display()); let config_str = match fs::read_to_string(&config_pathbuf) { Ok(c_str) => c_str, Err(e) => { error!( "Error loading configuration from {}: {}", config_pathbuf.display(), e ); std::process::exit(1); } }; let config: Config = match toml::from_str(&config_str) { Ok(config) => config, Err(e) => { error!( "Error parsing configuration in {}: {}", config_pathbuf.display(), e ); std::process::exit(1); } }; info!("Connecting to the database..."); let pool_config = AsyncDieselConnectionManager::::new(&config.database.url); let pool = match Pool::builder().build(pool_config).await { Ok(pool) => pool, Err(e) => { error!("Error while creating database pool: {}", e); std::process::exit(1); } }; info!("Running pending migrations..."); let local_config = config.clone(); let db_url = config.database.url.clone(); match actix_web::rt::task::spawn_blocking(move || { // Lock block let mut conn = match AsyncConnectionWrapper::::establish(&db_url) { Ok(conn) => conn, Err(e) => { error!("Error acquiring connection from pool: {}", e); std::process::exit(1); } }; match conn.run_pending_migrations(MIGRATIONS) { Ok(_) => (), Err(e) => { error!("Failed to run pending migrations: {}", e); std::process::exit(1); } } }) .await { Ok(_) => (), Err(e) => { error!("Error waiting for migrations: {}", e); std::process::exit(1); } } let key = Hmac::new_from_slice(config.server.application_key.as_bytes()).unwrap(); let stk_epoch = UNIX_EPOCH + Duration::from_secs(1616260136); let id_generator = SnowflakeIdGenerator::with_epoch( config.server.machine_id, config.server.node_id, stk_epoch, ); let app_state = Data::new(AppState { config, pool, idgen: Arc::new(Mutex::new(id_generator)), key, }); let server = HttpServer::new(move || { App::new() .app_data(JsonConfig::default().error_handler(|err, _rq| { Error::from({ let err2: APIErrorResponse = (&err).into(); actix_web::error::InternalError::from_response( err, HttpResponse::BadRequest().json(err2), ) }) })) .service(routes::signup::signup_req) .service(routes::login::user_login_req) .wrap(Logger::default()) .wrap(actix_cors::Cors::permissive()) .app_data(app_state.clone()) }) .bind((local_config.server.bind.ip, local_config.server.bind.port)) .unwrap(); server.run().await.unwrap(); info!("Goodbye!"); }