You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
4.2 KiB
124 lines
4.2 KiB
use std::{env, sync::Arc}; |
|
|
|
use axum::{ |
|
middleware::{from_fn, from_fn_with_state}, |
|
routing::{get, post}, |
|
Router, |
|
}; |
|
use dotenv::dotenv; |
|
use miette::IntoDiagnostic; |
|
use sea_orm::Database; |
|
use time::macros::format_description; |
|
use tower::ServiceBuilder; |
|
use tower_http::{services::ServeDir, trace::TraceLayer}; |
|
use tracing::info; |
|
use tracing_subscriber::{fmt::time::UtcTime, EnvFilter, FmtSubscriber}; |
|
|
|
mod data; |
|
mod middleware; |
|
mod utils; |
|
mod views; |
|
|
|
use data::{AppOptions, AppState}; |
|
|
|
#[tokio::main] |
|
async fn main() -> miette::Result<()> { |
|
dotenv().ok(); |
|
|
|
let listen_addr = env::var("LISTEN_ADDR") |
|
.unwrap_or("127.0.0.1:8080".to_string()) |
|
.parse() |
|
.expect("LISTEN_ADDR should be valid"); |
|
let db_conn = env::var("DATABASE_URL").expect("DATABASE_URL should be set"); |
|
let sqlx_filter = match env::var("V_LOG_SQL") { |
|
Ok(_) => "sqlx=info", |
|
Err(_) => "sqlx=warn", |
|
}; |
|
let domain = env::var("BASE_DOMAIN").ok(); |
|
let static_file_path = env::var("STATIC_FILE_PATH").unwrap_or("static".to_string()); |
|
let charset: Vec<char> = env::var("V_ID_CHARSET") |
|
.unwrap_or("abcdefghijkmnpqrstuwxyz123467890".to_string()) |
|
.chars() |
|
.collect(); |
|
let id_len = env::var("V_ID_GEN_LENGTH") |
|
.map(|len| { |
|
len.parse() |
|
.expect("V_ID_GEN_LENGTH should parse into an integer") |
|
}) |
|
.unwrap_or(6 as usize); |
|
let test_data = env::var("V_DEV_TEST_DATA").is_ok(); |
|
|
|
FmtSubscriber::builder() |
|
.with_env_filter( |
|
EnvFilter::from_default_env().add_directive(sqlx_filter.parse().into_diagnostic()?), |
|
) |
|
.with_timer(UtcTime::new(format_description!( |
|
"[year]:[month]:[day]T[hour]:[minute]:[second]" |
|
))) |
|
.init(); |
|
|
|
let db = Database::connect(db_conn.clone()).await.into_diagnostic()?; |
|
utils::migrate_and_seed_db(&db).await.into_diagnostic()?; |
|
if test_data { |
|
utils::insert_dev_test_data(&db, charset.clone(), id_len) |
|
.await |
|
.into_diagnostic()?; |
|
info!("V_DEV_TEST_DATA is enabled. credentials: dev:a-password"); |
|
} |
|
|
|
let state = Arc::new(AppState::new( |
|
AppOptions { |
|
domain, |
|
charset, |
|
id_len, |
|
}, |
|
db, |
|
None, |
|
)); |
|
state.seed_cache().await.into_diagnostic()?; |
|
|
|
let app = Router::new() |
|
.route("/", get(|| async { "landing page with login form" })) |
|
.route("/404", get(|| async { "404 not found " })) |
|
.route("/gay/login", post(views::auth::login)) // this one's excempt from the login requirement |
|
.nest_service("/gay/static", ServeDir::new(static_file_path)) |
|
.nest( |
|
"/gay", // everything is under this namespace to avoid blocking possible link uris as much as possible |
|
Router::new() |
|
.layer( |
|
ServiceBuilder::new() |
|
.layer(from_fn_with_state(state.clone(), middleware::fetch_user)) |
|
.layer(from_fn(middleware::reject_unauthenticated)), |
|
) |
|
.route("/", get(|| async { "logged in dashboard" })) |
|
.route("/logout", get(views::auth::logout)) |
|
.nest("/account", views::user::get_selfservice_routes()) |
|
.nest("/link", views::link::get_routes()) |
|
.nest( |
|
"/admin", |
|
views::admin::get_routes() |
|
.layer(from_fn(middleware::admin_only)) |
|
.nest("/user", views::user::get_admin_routes()), |
|
), |
|
) |
|
.route("/:source", get(views::link::access)) |
|
.layer( |
|
ServiceBuilder::new() // all the middleware here is very cheap. |
|
.layer(TraceLayer::new_for_http()) |
|
.layer(from_fn(middleware::errors_for_humans)) |
|
.layer(from_fn_with_state( |
|
state.clone(), |
|
middleware::try_get_user_session, |
|
)), |
|
) |
|
.with_state(state.clone()); |
|
|
|
info!("listening on {}", &listen_addr); |
|
|
|
axum::Server::bind(&listen_addr) |
|
.serve(app.into_make_service()) |
|
.await |
|
.into_diagnostic()?; |
|
|
|
Ok(()) |
|
}
|
|
|