mod auth; mod endpoints; mod error; mod middleware; mod user; mod validate; use std::env; use actix_web::{ App, HttpServer, middleware::from_fn, rt::System, web::{self}, }; use aws_config::BehaviorVersion; use sqlx::PgPool; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, }; use crate::{ auth::{Auth, JWT}, user::UserRepository, }; struct AppState { reqwest_client: reqwest::Client, auth: Auth, user: UserRepository, } async fn run() -> std::io::Result<()> { let reqwest_client = reqwest::Client::builder() .user_agent(concat!( env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION") )) .build() .expect("failed to create reqwest client"); let config = aws_config::load_defaults(BehaviorVersion::v2026_01_12()).await; let dynamodb_client = aws_sdk_dynamodb::Client::new(&config); let app_data = web::Data::new(AppState { reqwest_client: reqwest_client.clone(), auth: Auth::JWT(JWT::new(reqwest_client.clone())), user: UserRepository::new( PgPool::connect( &env::var("DATABASE_URL").expect("DATABASE_URL environment variable must be set"), ) .await .expect("error connecting to db"), dynamodb_client, ), }); HttpServer::new(move || { App::new() .app_data(app_data.clone()) .wrap( sentry::integrations::actix::Sentry::builder() .capture_server_errors(true) .start_transaction(true) .finish(), ) .wrap(tracing_actix_web::TracingLogger::default()) .wrap(actix_cors::Cors::permissive()) .route( "/", web::get() .to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))), ) .service( web::scope("/api").service( web::scope("/v0") .service( web::scope("/user") .route("/repos", web::get().to(endpoints::get_repos::get_repos)) .wrap(from_fn(middleware::protected)) .route( "/repos/search", web::get().to(endpoints::search_repos::search_repos), ) .wrap(from_fn(middleware::protected)) .route("/repo/add", web::post().to(endpoints::add_repo::add_repo)) .wrap(from_fn(middleware::protected)), ) .route( "/repos", web::get().to(endpoints::global_repos::global_repos), ) .route( "/repos/{repo_id}/files/{file:.*}", web::get().to(endpoints::proxy_file::proxy_file), ), ), ) }) .bind((env::var("ADDRESS").unwrap_or("0.0.0.0".to_string()), 8080))? .run() .await } // cant use #[actix_web::main] because of Sentry // Note: "Macros like #[tokio::main] and #[actix_web::main] are not supported. The Sentry client must be initialized before the async runtime is started." // https://docs.sentry.io/platforms/rust/guides/actix-web/ fn main() -> std::io::Result<()> { let tracing_env_filter = EnvFilter::builder() .with_default_directive(LevelFilter::INFO.into()) .from_env_lossy() .add_directive("reqwest=info".parse().unwrap()) .add_directive("hyper=info".parse().unwrap()) .add_directive("h2=info".parse().unwrap()) .add_directive("rustls=info".parse().unwrap()) .add_directive("aws=info".parse().unwrap()); tracing_subscriber::registry() .with(tracing_env_filter) .with( tracing_subscriber::fmt::layer() .compact() .with_span_events(FmtSpan::CLOSE), ) .with(sentry::integrations::tracing::layer()) .init(); let guard = sentry::init(( env::var("SENTRY_DSN").ok(), sentry::ClientOptions { release: sentry::release_name!(), traces_sample_rate: 1.0, session_mode: sentry::SessionMode::Request, debug: true, ..Default::default() }, )); if guard.is_enabled() { tracing::info!("sentry initialized"); } else { tracing::info!("sentry **NOT** initialized") }; System::new().block_on(run()) }