Files
ghostv2/api/src/main.rs

148 lines
4.8 KiB
Rust

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())
}