use crate::config::Config; use crate::error::APIErrorResponse; use crate::models::user::User; use actix_web::middleware::Logger; use actix_web::web::{Data, JsonConfig}; use actix_web::{App, Error, HttpResponse, HttpServer}; use actix_web_prom::PrometheusMetricsBuilder; use bson::doc; use hmac::digest::KeyInit; use hmac::Hmac; use log::{error, info}; use mongodb::options::{ClientOptions, IndexOptions, ResolverConfig}; use mongodb::{Client, IndexModel}; 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, pub config: Config, pub db: Client, } #[actix_web::main] async fn main() -> Result<(), Box> { 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: carica "); std::process::exit(1); } }; let config_pathbuf = PathBuf::from(config_path); info!("Carica 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 database = client.database(&config.mongodb_database); let col_users = database.collection::("users"); col_users .create_index(unique!(doc! {"email": 1}), None) .await?; let state = AppState { key, config: config.clone(), db: client, }; info!("Database connected, state loaded"); info!("Prometheus init"); let prom = PrometheusMetricsBuilder::new("carica") .endpoint("/metrics") .build() .expect("Prometheus setup failed"); let server = HttpServer::new(move || { App::new() .wrap(prom.clone()) .service(routes::v1::users::create::create_user_req) .service(routes::v1::users::login::user_login_req) .service(routes::v1::users::profile::user_profile_req) .service(routes::v1::questions::create::create_question_req) .service(routes::v1::questions::get::get_question_req) .service(routes::v1::questions::delete::delete_question_req) .service(routes::v1::tracks::create::create_track_req) .service(routes::v1::tracks::get::get_track_req) .service(routes::v1::tracks::update::update_track_req) .service(routes::v1::tracks::list::list_tracks_req) .service(routes::v1::tracks::register::register_track_req) .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(()) }