2024-07-30 11:37:54 +01:00
use actix_cors ::Cors ;
use actix_governor ::{ Governor , GovernorConfigBuilder } ;
use actix_web ::{
2024-08-12 10:55:21 +01:00
middleware ::{ from_fn , Compress , Condition , Logger , NormalizePath , TrailingSlash } ,
2024-07-30 11:37:54 +01:00
rt ::System ,
web , App , HttpServer ,
} ;
use log ::info ;
use rusty_s3 ::{ Bucket , Credentials , UrlStyle } ;
2024-09-03 15:01:48 +01:00
use std ::{ env ::current_dir , fs ::create_dir_all , path ::PathBuf , sync ::Mutex } ;
2024-07-30 11:37:54 +01:00
use pesde ::{
source ::{ pesde ::PesdePackageSource , traits ::PackageSource } ,
AuthConfig , Project ,
} ;
use crate ::{ auth ::UserIdExtractor , search ::make_search } ;
mod auth ;
mod endpoints ;
mod error ;
mod package ;
mod search ;
pub struct AppState {
pub s3_bucket : Bucket ,
pub s3_credentials : Credentials ,
pub source : Mutex < PesdePackageSource > ,
pub project : Project ,
pub reqwest_client : reqwest ::Client ,
pub search_reader : tantivy ::IndexReader ,
pub search_writer : Mutex < tantivy ::IndexWriter > ,
}
#[ macro_export ]
macro_rules ! benv {
( $name :expr ) = > {
std ::env ::var ( $name )
} ;
( $name :expr = > $default :expr ) = > {
benv! ( $name ) . unwrap_or ( $default . to_string ( ) )
} ;
( required $name :expr ) = > {
benv! ( $name ) . expect ( concat! ( " Environment variable ` " , $name , " ` must be set " ) )
} ;
( parse $name :expr ) = > {
benv! ( $name )
. map ( | v | v . parse ( ) . expect ( concat! (
" Environment variable ` " ,
$name ,
" ` must be a valid value "
) ) )
} ;
( parse required $name :expr ) = > {
benv! ( parse $name ) . expect ( concat! ( " Environment variable ` " , $name , " ` must be set " ) )
} ;
( parse $name :expr = > $default :expr ) = > {
benv! ( $name = > $default )
. parse ( )
. expect ( concat! (
" Environment variable ` " ,
$name ,
" ` must a valid value "
) )
} ;
}
async fn run ( with_sentry : bool ) -> std ::io ::Result < ( ) > {
let address = benv! ( " ADDRESS " = > " 127.0.0.1 " ) ;
let port : u16 = benv! ( parse " PORT " = > " 8080 " ) ;
let cwd = current_dir ( ) . unwrap ( ) ;
let data_dir = cwd . join ( " data " ) ;
create_dir_all ( & data_dir ) . unwrap ( ) ;
let project = Project ::new (
& cwd ,
2024-09-03 15:01:48 +01:00
None ::< PathBuf > ,
2024-07-30 11:37:54 +01:00
data_dir . join ( " project " ) ,
& cwd ,
AuthConfig ::new ( ) . with_git_credentials ( Some ( gix ::sec ::identity ::Account {
username : benv ! ( required " GITHUB_USERNAME " ) ,
password : benv ! ( required " GITHUB_PAT " ) ,
} ) ) ,
) ;
2024-09-04 15:19:09 +01:00
let source = PesdePackageSource ::new ( benv! ( required " INDEX_REPO_URL " ) . try_into ( ) . unwrap ( ) ) ;
2024-07-30 11:37:54 +01:00
source . refresh ( & project ) . expect ( " failed to refresh source " ) ;
let ( search_reader , search_writer ) = make_search ( & project , & source ) ;
let app_data = web ::Data ::new ( AppState {
s3_bucket : Bucket ::new (
benv! ( parse required " S3_ENDPOINT " ) ,
UrlStyle ::Path ,
benv! ( required " S3_BUCKET_NAME " ) ,
benv! ( required " S3_REGION " ) ,
)
. unwrap ( ) ,
s3_credentials : Credentials ::new (
benv! ( required " S3_ACCESS_KEY " ) ,
benv! ( required " S3_SECRET_KEY " ) ,
) ,
source : Mutex ::new ( source ) ,
project ,
reqwest_client : reqwest ::ClientBuilder ::new ( )
. user_agent ( concat! (
env! ( " CARGO_PKG_NAME " ) ,
" / " ,
env! ( " CARGO_PKG_VERSION " )
) )
. build ( )
. unwrap ( ) ,
search_reader ,
search_writer : Mutex ::new ( search_writer ) ,
} ) ;
let publish_governor_config = GovernorConfigBuilder ::default ( )
. key_extractor ( UserIdExtractor )
. burst_size ( 12 )
. per_second ( 60 )
. use_headers ( )
. finish ( )
. unwrap ( ) ;
info! ( " listening on {address}:{port} " ) ;
HttpServer ::new ( move | | {
App ::new ( )
. wrap ( Condition ::new ( with_sentry , sentry_actix ::Sentry ::new ( ) ) )
. wrap ( NormalizePath ::new ( TrailingSlash ::Trim ) )
. wrap ( Cors ::permissive ( ) )
. wrap ( Logger ::default ( ) )
. wrap ( Compress ::default ( ) )
. app_data ( app_data . clone ( ) )
. route (
" / " ,
web ::get ( ) . to ( | | async {
concat! ( env! ( " CARGO_PKG_NAME " ) , " / " , env! ( " CARGO_PKG_VERSION " ) )
} ) ,
)
. service (
web ::scope ( " /v0 " )
2024-08-27 20:10:24 +01:00
. route ( " /search " , web ::get ( ) . to ( endpoints ::search ::search_packages ) )
2024-07-30 11:37:54 +01:00
. route (
" /packages/{name} " ,
2024-08-27 20:10:24 +01:00
web ::get ( ) . to ( endpoints ::package_versions ::get_package_versions ) ,
2024-07-30 11:37:54 +01:00
)
. route (
" /packages/{name}/{version}/{target} " ,
2024-08-27 20:10:24 +01:00
web ::get ( ) . to ( endpoints ::package_version ::get_package_version ) ,
2024-07-30 11:37:54 +01:00
)
. route (
" /packages " ,
web ::post ( )
. to ( endpoints ::publish_version ::publish_package )
. wrap ( Governor ::new ( & publish_governor_config ) )
. wrap ( from_fn ( auth ::authentication ) ) ,
) ,
)
} )
. bind ( ( address , port ) ) ?
. run ( )
. await
}
// can't 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 so that all threads are correctly connected to the Hub."
// https://docs.sentry.io/platforms/rust/guides/actix-web/
fn main ( ) -> std ::io ::Result < ( ) > {
let _ = dotenvy ::dotenv ( ) ;
let sentry_url = benv! ( " SENTRY_URL " ) . ok ( ) ;
let with_sentry = sentry_url . is_some ( ) ;
let mut log_builder = pretty_env_logger ::formatted_builder ( ) ;
log_builder . parse_env ( pretty_env_logger ::env_logger ::Env ::default ( ) . default_filter_or ( " info " ) ) ;
if with_sentry {
let logger = sentry_log ::SentryLogger ::with_dest ( log_builder . build ( ) ) ;
log ::set_boxed_logger ( Box ::new ( logger ) ) . unwrap ( ) ;
log ::set_max_level ( log ::LevelFilter ::Info ) ;
} else {
log_builder . try_init ( ) . unwrap ( ) ;
}
let _guard = if let Some ( sentry_url ) = sentry_url {
std ::env ::set_var ( " RUST_BACKTRACE " , " 1 " ) ;
Some ( sentry ::init ( (
sentry_url ,
sentry ::ClientOptions {
release : sentry ::release_name! ( ) ,
.. Default ::default ( )
} ,
) ) )
} else {
None
} ;
System ::new ( ) . block_on ( run ( with_sentry ) )
}