// retrigger-build-marker-6 // retrigger-build-marker-4 #![allow(dead_code)] mod ai; mod ai_credits; mod ai_subscription; mod handlers; mod litellm; mod mail; use axum::{routing::get, Router}; use std::net::SocketAddr; use std::sync::Arc; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use sqlx::PgPool; use mail::Mailer; #[derive(Clone)] pub struct AppState { pub pool: PgPool, pub mail: Arc, pub redis: cache::RedisPool, pub storage: Arc, } #[tokio::main] async fn main() { tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::new( std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()), )) .with(tracing_subscriber::fmt::layer()) .init(); // Fail fast — critical env vars must be present before binding any port std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = db::establish_connection(&database_url) .await .expect("Failed to connect to the database"); tracing::info!("Connected to the database"); let redis_url = std::env::var("REDIS_URL") .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string()); let redis = cache::connect(&redis_url) .await .expect("Failed to connect to Redis"); tracing::info!("Connected to Redis"); let mailer = Arc::new(Mailer::new()); let storage = Arc::new(storage::StorageClient::from_env().await); let state = AppState { pool, mail: mailer, redis, storage, }; let app = Router::new() // ── Auth ───────────────────────────────────────────────────────── .nest("/api/auth", handlers::auth::router()) // ── V1 API (backward compatibility) ─────────────────────────────── .nest("/api/v1/users", handlers::auth::v1_router()) // ── Roles & User Self-Service ───────────────────────────────────── .nest("/api/admin/roles", handlers::roles::router()) .nest("/api/admin/permissions", handlers::permissions::router()) .nest("/api/admin/external-roles", handlers::external_roles::router()) .merge(handlers::modules::persona_types_router()) .merge(handlers::modules::modules_router()) .merge(handlers::modules::role_modules_router()) .nest("/api/admin/users", handlers::admin::router()) .nest("/api/me/roles", handlers::user_roles::router()) // ── Notifications ───────────────────────────────────────────────── .nest("/api/me/notifications", handlers::notifications::router()) .nest("/api/me/settings", handlers::settings::router()) // ── Admin: Approvals (jobs/requirements) ───────────────────────── .nest("/api/admin/approvals", handlers::approvals::router()) .nest("/api/admin/verifications", handlers::verifications::router()) // ── Me: Profile Status + Verification Status ────────────────────── .nest("/api/me", handlers::profile::me_router()) // ── Profile (save + submit-for-verification) ────────────────────── .nest("/api/profile", handlers::profile::router()) // ── Onboarding State (legacy, kept for compatibility) ──────────── .nest("/api/onboarding", handlers::onboarding::onboarding_router()) // ── Admin: Onboarding + Dashboard Config ────────────────────────── .nest("/api/admin/onboarding-config", handlers::config::onboarding_router()) .nest("/api/admin/dashboard-config", handlers::config::dashboard_router()) .nest("/api/admin/dashboard", handlers::dashboard::router()) .nest("/api/admin/runtime-configs", handlers::config::admin_runtime_router()) // ── Admin: Activity Logs (Audit) ─────────────────────────────────── .nest("/api/admin/activity-logs", handlers::activity_logs::router()) // ── Public Config ───────────────────────────────────────────────── .nest("/api/config/onboarding", handlers::config::onboarding_router()) .nest("/api/config/dashboard", handlers::config::dashboard_router()) .nest("/api/runtime-config", handlers::config::runtime_router()) // ── Knowledge Base (public) ─────────────────────────────────────── .nest("/api/kb", handlers::kb::public_router()) // ── Knowledge Base (admin) ──────────────────────────────────────── .nest("/api/admin/kb", handlers::kb::admin_router()) // ── Support Tickets (user-facing) ───────────────────────────────── .nest("/api/support/tickets", handlers::support::user_router()) // ── Support Tickets (admin) ─────────────────────────────────────── .nest("/api/admin/support-cases", handlers::support::admin_router()) // ── Reference number lookup (AI assistant) ──────────────────────── .nest("/api/support", handlers::support::reference_router()) // ── Reviews (public + admin) ───────────────────────────────────── .nest("/api/reviews", handlers::reviews::public_router()) .nest("/api/admin/reviews", handlers::reviews::admin_router()) // ── Coupons & Discounts (admin) ─────────────────────────────────── .nest("/api/admin/coupons", handlers::coupons::coupons_router()) .nest("/api/admin/discounts", handlers::coupons::discounts_router()) .nest("/api/admin/payment-gateway-config", handlers::payment_gateway::router()) // ── Tracecoin Packages (public) ─────────────────────────────────── .nest("/api/packages", handlers::pricing::public_packages_router()) // ── Tracecoin Packages & Reports (admin) ────────────────────────── .nest("/api/admin/tracecoin-packages", handlers::pricing::packages_router()) .nest("/api/admin/reports", handlers::pricing::reports_router()) // ── Email Management (admin) ────────────────────────────────────── .nest("/api/admin/email", handlers::admin_email::router()) // ── AI Management (admin) ──────────────────────────────────────── .nest("/api/admin/ai", handlers::admin_ai::admin_ai_router()) // ── AI Assistant ────────────────────────────────────────────────── .nest("/api/ai", handlers::ai::ai_router()) // ── Ask Ash AI Credits (Phase 1) ──────────────────────────────────── .nest("/api/ai-credits", handlers::ai_credits::router()) .nest("/internal/users", handlers::internal::router()) .nest("/api/admin/ai-credits", handlers::ai_credits::admin_router()) .route("/health", get(|| async { "Users OK" })) .with_state(state); let port: u16 = std::env::var("PORT") .unwrap_or_else(|_| "9101".to_string()) .parse() .expect("PORT must be a valid u16"); let addr = SocketAddr::from(([0, 0, 0, 0], port)); tracing::info!("Users service listening on {}", addr); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); let _app = axum::serve(listener, app).await.unwrap(); }