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 actix_web_prom::PrometheusMetricsBuilder;
use hmac::digest::KeyInit;
use hmac::Hmac;
use log::{error, info};
use mongodb::options::{ClientOptions, ResolverConfig};
use mongodb::Client;
use sha2::Sha256;
use std::fs;
use std::path::PathBuf;
pub mod error;
#[macro_use]
pub mod macros;
pub mod config;
pub mod response;
pub mod tokens;
#[derive(Clone)]
pub struct AppState {
pub key: Hmac<Sha256>,
pub config: Config,
pub db: Client,
}
#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
better_panic::install();
simple_logger::init_with_env().unwrap();
// load config
let mut args = std::env::args();
let config_path = match args.nth(1) {
Some(path) => path,
None => {
eprintln!("usage: starkingdoms-api <config_path>");
std::process::exit(1);
}
};
let config_pathbuf = PathBuf::from(config_path);
info!(
"StarKingdoms API v{} starting up",
env!("CARGO_PKG_VERSION")
);
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!("Creating JWT key");
let key = Hmac::new_from_slice(config.application_key.as_bytes()).unwrap();
// get db from env
info!("Connecting to database at {}", config.mongodb_url);
let options = ClientOptions::parse_with_resolver_config(
&config.mongodb_url,
ResolverConfig::cloudflare(),
)
.await?;
let client = Client::with_options(options)?;
info!("Creating user email unique index");
let state = AppState {
key,
config: config.clone(),
db: client,
};
info!("Database connected, state loaded");
info!("Prometheus init");
let prom = PrometheusMetricsBuilder::new("starkingdoms_api")
.endpoint("/metrics")
.build()
.expect("Prometheus setup failed");
let server = HttpServer::new(move || {
App::new()
.wrap(prom.clone())
.app_data(JsonConfig::default().error_handler(|err, _req| {
Error::from({
let err2: APIErrorResponse = (&err).into();
actix_web::error::InternalError::from_response(
err,
HttpResponse::BadRequest().json(err2),
)
})
}))
.wrap(Logger::default())
.app_data(Data::new(state.clone()))
.wrap(actix_cors::Cors::permissive())
})
.bind(("0.0.0.0", 8080))?;
info!("HttpServer created, bound");
info!("HttpServer start");
server.run().await?;
info!("Goodbye!");
Ok(())
}