mod config; mod db; mod models; mod routes; mod storage; use std::sync::Arc; use axum::{ routing::{delete, get, post, put}, Router, }; use sqlx::any::install_default_drivers; use tower_http::trace::TraceLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use crate::{config::AppConfig, db::Database, storage::S3Storage}; /// Shared application state injected into every handler. #[derive(Clone)] pub struct AppState { pub db: Database, pub storage: S3Storage, } #[tokio::main] async fn main() { // Initialise structured logging. tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "mikebase=debug,tower_http=debug".into()), ) .with(tracing_subscriber::fmt::layer()) .init(); // Load configuration from config.toml / environment variables. let cfg = AppConfig::load().expect("Failed to load configuration"); let cfg = Arc::new(cfg); // Install SQLite and PostgreSQL drivers for sqlx AnyPool. install_default_drivers(); // Connect to the database and run migrations. let db = Database::connect(cfg.clone()) .await .expect("Failed to connect to database"); db.migrate().await.expect("Failed to run migrations"); // Build S3 storage client. let storage = S3Storage::new(&cfg); let state = AppState { db, storage }; let app = Router::new() .route("/", get(routes::emotes::root)) .route("/health", get(routes::health::health)) .route("/version", get(routes::version::version)) .route("/json", get(routes::emotes::list_emotes)) .route("/emotes", post(routes::emotes::create_emote)) .route("/emotes/{uuid}", put(routes::emotes::update_emote)) .route("/emotes/{uuid}", delete(routes::emotes::delete_emote)) .layer(TraceLayer::new_for_http()) .with_state(state); let addr = format!("{}:{}", cfg.server.host, cfg.server.port); tracing::info!("Listening on {addr}"); let listener = tokio::net::TcpListener::bind(&addr) .await .expect("Failed to bind address"); axum::serve(listener, app) .await .expect("Server error"); }