mod config; mod firefly; mod receipts; mod routes; mod transactions; use rocket::fairing::{self, AdHoc}; use rocket::fs::FileServer; use rocket::response::{Redirect, Responder}; use rocket::serde::json::Json; use rocket::Rocket; use rocket_db_pools::Database as RocketDatabase; use rocket_dyn_templates::Template; use serde::Serialize; use tracing::{debug, error, info}; use config::Config; use firefly::Firefly; struct Context { #[allow(dead_code)] config: Config, firefly: Firefly, } #[derive(Debug, thiserror::Error)] enum InitError { #[error("Invalid config: {0}")] Config(std::io::Error), } impl Context { pub fn init(config: Config) -> Result { let token = std::fs::read_to_string(&config.firefly.token) .map_err(InitError::Config)?; let firefly = Firefly::new(&config.firefly.url, &token); Ok(Self { config, firefly }) } } #[derive(RocketDatabase)] #[database("receipts")] struct Database(rocket_db_pools::sqlx::PgPool); #[derive(Serialize)] struct ErrorBody { error: String, } #[derive(Responder)] #[response(status = 500)] struct ErrorResponse { error: Json, } impl ErrorResponse { pub fn new>(error: S) -> Self { Self { error: Json(ErrorBody { error: error.into(), }), } } } #[rocket::get("/")] async fn index() -> Redirect { Redirect::to("/receipts") } async fn run_migrations(rocket: Rocket) -> fairing::Result { if let Some(db) = Database::fetch(&rocket) { info!("Applying database migrations"); if let Err(e) = sqlx::migrate!("./migrations").run(&db.0).await { error!("Database migration failed: {}", e); Err(rocket) } else { Ok(rocket) } } else { Err(rocket) } } #[rocket::launch] async fn rocket() -> _ { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_writer(std::io::stderr) .init(); let rocket = rocket::build(); let figment = rocket.figment(); let config: Config = figment.extract().unwrap(); let ctx = match Context::init(config) { Ok(c) => c, Err(e) => { error!("Failed to initialize application context: {}", e); std::process::exit(1); }, }; debug!("Using Firefly III URL {}", &ctx.firefly.url()); rocket .manage(ctx) .mount("/", rocket::routes![index]) .mount("/transactions", routes::transactions::routes()) .mount("/receipts", routes::receipts::routes()) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) .attach(Database::init()) .attach(AdHoc::try_on_ignite("Migrate Database", run_migrations)) }