mod common; mod auth; mod tasks; mod audit; mod developers; mod main_view; mod docs; use axum::{extract::FromRef, Router}; use std::net::SocketAddr; use tracing::info; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use crate::common::config::Config; use crate::common::database::connect_db; use crate::auth::repository::MongoUserRepository; use crate::tasks::repository::MongoTaskRepository; use crate::developers::repository::MongoDeveloperRepository; use crate::audit::repository::MongoAuditRepository; #[derive(Clone)] struct AppState { config: Config, db: mongodb::Database, user_repo: MongoUserRepository, task_repo: MongoTaskRepository, dev_repo: MongoDeveloperRepository, audit_repo: MongoAuditRepository, } impl FromRef for Config { fn from_ref(state: &AppState) -> Self { state.config.clone() } } impl FromRef for mongodb::Database { fn from_ref(state: &AppState) -> Self { state.db.clone() } } impl FromRef for MongoUserRepository { fn from_ref(state: &AppState) -> Self { state.user_repo.clone() } } impl FromRef for MongoTaskRepository { fn from_ref(state: &AppState) -> Self { state.task_repo.clone() } } impl FromRef for MongoDeveloperRepository { fn from_ref(state: &AppState) -> Self { state.dev_repo.clone() } } impl FromRef for MongoAuditRepository { fn from_ref(state: &AppState) -> Self { state.audit_repo.clone() } } #[tokio::main] async fn main() -> Result<(), Box> { // 1. Initialize logging tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer()) .init(); info!("Starting Stick Template application..."); // 2. Parse config from env let config = Config::from_env(); // 3. Connect to MongoDB let db = connect_db(&config).await?; // 4. Initialize repositories let user_repo = MongoUserRepository::new(db.clone()); let task_repo = MongoTaskRepository::new(db.clone()); let dev_repo = MongoDeveloperRepository::new(db.clone()); let audit_repo = MongoAuditRepository::new(db.clone()); // Auto-provision initial administrator if users collection is empty let users_count = db.collection::("users") .count_documents(mongodb::bson::doc! {}) .await?; if users_count == 0 { use rand::{distributions::Alphanumeric, Rng}; let random_password: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(16) .map(char::from) .collect(); let password_hash = bcrypt::hash(&random_password, bcrypt::DEFAULT_COST)?; let admin_username = "admin"; let admin_user = crate::auth::models::User { id: None, username: admin_username.to_string(), password_hash, is_admin: true, created_at: chrono::Utc::now(), }; db.collection::("users") .insert_one(admin_user) .await?; info!("\n\n\ ======================================================\n\ CREATED INITIAL ADMINISTRATOR ACCOUNT:\n\ Username: {}\n\ Password: {}\n\ ======================================================\n\n", admin_username, random_password); } // 5. Initialize shared AppState let state = AppState { config: config.clone(), db, user_repo, task_repo, dev_repo, audit_repo, }; // 6. Build and merge routers by use-case let app = Router::new() .merge(main_view::router()) .merge(docs::router()) .merge(auth::router()) .merge(tasks::router()) .merge(developers::router()) .merge(audit::router()) .with_state(state); // 7. Bind address and run server let host_addr: SocketAddr = format!("{}:{}", config.host, config.port) .parse() .expect("Invalid HOST or PORT config"); info!("Listening on http://{}", host_addr); let listener = tokio::net::TcpListener::bind(host_addr).await?; axum::serve(listener, app.into_make_service_with_connect_info::()).await?; Ok(()) }