use std::fs; use std::path::PathBuf; use actix_web::{App, Error, HttpResponse, HttpServer}; use actix_web::middleware::Logger; use actix_web::web::{Data, JsonConfig}; use diesel::Connection; use diesel_async::async_connection_wrapper::AsyncConnectionWrapper; use diesel_async::AsyncPgConnection; use diesel_async::pooled_connection::AsyncDieselConnectionManager; use diesel_async::pooled_connection::bb8::Pool; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use log::{error, info}; use crate::config::Config; use crate::error::APIErrorResponse; pub mod error; #[macro_use] pub mod response; pub mod config; pub mod routes; pub mod schema; pub mod models; #[macro_use] pub mod id; pub mod email; #[derive(Clone)] pub struct AppState { pub config: Config, pub pool: bb8::Pool> } pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); #[actix_web::main] async fn main() { env_logger::init(); info!("Trifid 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: trifid-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 app_state = Data::new(AppState { config, pool }); 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::v1::signup::signup_req) .service(routes::v1::auth::verify_magic_link::verify_link_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!"); }