#[macro_use] extern crate rocket; use clap::Parser; mod demo; mod feed_utils; mod feeds; mod poll; mod user; use rocket::fairing::{self, AdHoc}; use rocket::fs::FileServer; use rocket::response::Redirect; use rocket::{Build, Rocket, State}; use rocket_db_pools::{sqlx, Connection, Database}; use rocket_dyn_templates::{context, Template}; use user::AuthenticatedUser; /// RSS Reader application #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Path to the SQLite database file #[arg(short, long)] database: Option, /// Run in demo mode with an in-memory database #[arg(long)] demo: bool, } #[derive(Database)] #[database("rss_data")] struct Db(sqlx::SqlitePool); #[get("/")] fn index(_user: AuthenticatedUser) -> Template { Template::render("index", context! {}) } #[get("/", rank = 2)] async fn index_redirect(mut db: Connection) -> Redirect { // Check if any users exist let count = sqlx::query!("SELECT COUNT(*) as count FROM users") .fetch_one(&mut **db) .await .map_err(|_| Redirect::to(uri!(login))) .unwrap() .count; if count == 0 { Redirect::to(uri!(user::setup_page)) } else { Redirect::to(uri!(login)) } } #[get("/login")] fn login(demo_mode: &State) -> Template { Template::render("login", context! { demo_mode: **demo_mode }) } // Run migrations and setup demo data if needed async fn setup_database(demo: bool, rocket: Rocket) -> fairing::Result { let db = match Db::fetch(&rocket) { Some(db) => db, None => return Err(rocket), }; let pool = db.0.clone(); sqlx::migrate!("./migrations") .run(&pool) .await .expect("Failed to run database migrations"); if demo { demo::setup_demo_data(&pool).await; } Ok(rocket) } #[launch] fn rocket() -> _ { let args = Args::parse(); let db_url = if args.demo { "sqlite::memory:".to_string() } else { let database = args .database .expect("Database path is required when not in demo mode"); // Check if database file exists, create it if it doesn't if !std::path::Path::new(&database).exists() { use std::fs::File; File::create(&database).expect("Failed to create database file"); } format!("sqlite:{}", database) }; let figment = rocket::Config::figment().merge(("databases.rss_data.url", db_url)); rocket::custom(figment) .mount( "/", routes![ index, index_redirect, login, user::create_user, user::get_users, user::delete_user, user::login, user::logout, user::setup_page, user::setup, feeds::create_feed, feeds::get_feed, feeds::list_feeds, feeds::delete_feed, poll::poll_feed, ], ) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) .attach(Db::init()) .manage(args.demo) .attach(AdHoc::try_on_ignite("DB Setup", move |rocket| async move { setup_database(args.demo, rocket).await })) }