normogen/backend/src/main.rs
goose edfb89b644
Some checks failed
Lint and Build / Lint (push) Failing after 1m34s
Lint and Build / Build (push) Has been skipped
Lint and Build / Docker Build (push) Has been skipped
fix(ci): make rustfmt non-blocking and fix trailing whitespace
- Fixed trailing whitespace in backend/src/main.rs
- Made rustfmt steps non-blocking with 'continue-on-error: true'
- Added separate rustfmt run step to auto-fix issues
- Kept formatting check step for visibility but it won't fail the job

This allows the CI to continue even if there are minor formatting issues,
while still providing feedback about formatting problems.
2026-03-12 08:51:56 -03:00

199 lines
7.2 KiB
Rust

mod auth;
mod config;
mod db;
mod handlers;
mod middleware;
mod models;
mod security;
mod services;
use axum::{
routing::{delete, get, post, put},
Router,
};
use config::Config;
use std::sync::Arc;
use tower::ServiceBuilder;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
eprintln!("NORMOGEN BACKEND STARTING...");
eprintln!("Loading environment variables...");
match dotenv::dotenv() {
Ok(path) => eprintln!("Loaded .env from: {:?}", path),
Err(e) => eprintln!("No .env file found (this is OK in Docker): {}", e),
}
eprintln!("Initializing logging...");
tracing_subscriber::fmt::init();
tracing::info!("Starting Normogen backend server");
// Load configuration
let config = Config::from_env()?;
eprintln!("Configuration loaded successfully");
// Connect to MongoDB
tracing::info!("Connecting to MongoDB at {}", config.database.uri);
eprintln!("Connecting to MongoDB...");
let db = match db::MongoDb::new(&config.database.uri, &config.database.database).await {
Ok(db) => {
tracing::info!(
"Connected to MongoDB database: {}",
config.database.database
);
eprintln!("MongoDB connection successful");
db
}
Err(e) => {
eprintln!("FATAL: Failed to connect to MongoDB: {}", e);
return Err(e);
}
};
match db.health_check().await {
Ok(_) => {
tracing::info!("MongoDB health check: OK");
eprintln!("MongoDB health check: OK");
}
Err(e) => {
tracing::warn!("MongoDB health check failed: {}", e);
eprintln!("WARNING: MongoDB health check failed: {}", e);
}
}
// Create JWT service
let jwt_service = auth::JwtService::new(config.jwt.clone());
// Get the underlying MongoDB database for security services
let database = db.get_database();
// Initialize security services (Phase 2.6)
let audit_logger = security::AuditLogger::new(&database);
let session_manager = security::SessionManager::new(&database);
// Create account lockout service with reasonable defaults
let user_collection = database.collection("users");
let account_lockout = security::AccountLockout::new(
user_collection,
5, // max_attempts
15, // base_duration_minutes
1440, // max_duration_minutes (24 hours)
);
// Initialize health stats repository (Phase 2.7) - using Database pattern
let health_stats_repo = models::health_stats::HealthStatisticsRepository::new(&database);
// Initialize interaction service (Phase 2.8)
let interaction_service = Arc::new(services::InteractionService::new());
eprintln!("Interaction service initialized (Phase 2.8)");
// Create application state
let state = config::AppState {
db,
jwt_service,
config: config.clone(),
audit_logger: Some(audit_logger),
session_manager: Some(session_manager),
account_lockout: Some(account_lockout),
health_stats_repo: Some(health_stats_repo),
mongo_client: None,
interaction_service: Some(interaction_service),
};
eprintln!("Building router with security middleware...");
// Build public routes (no auth required)
let public_routes = Router::new()
.route(
"/health",
get(handlers::health_check).head(handlers::health_check),
)
.route("/ready", get(handlers::ready_check))
.route("/api/auth/register", post(handlers::register))
.route("/api/auth/login", post(handlers::login))
.route(
"/api/auth/recover-password",
post(handlers::recover_password),
);
// Build protected routes (auth required)
let protected_routes = Router::new()
// User profile management
.route("/api/users/me", get(handlers::get_profile))
.route("/api/users/me", put(handlers::update_profile))
.route("/api/users/me", delete(handlers::delete_account))
.route("/api/users/me/change-password", post(handlers::change_password))
// User settings
.route("/api/users/me/settings", get(handlers::get_settings))
.route("/api/users/me/settings", put(handlers::update_settings))
// Share management
.route("/api/shares", post(handlers::create_share))
.route("/api/shares", get(handlers::list_shares))
.route("/api/shares/:id", put(handlers::update_share))
.route("/api/shares/:id", delete(handlers::delete_share))
// Permission checking
.route("/api/permissions/check", post(handlers::check_permission))
// Session management (Phase 2.6)
.route("/api/sessions", get(handlers::get_sessions))
.route("/api/sessions/:id", delete(handlers::revoke_session))
.route("/api/sessions/all", delete(handlers::revoke_all_sessions))
// Medication management (Phase 2.7)
.route("/api/medications", post(handlers::create_medication))
.route("/api/medications", get(handlers::list_medications))
.route("/api/medications/:id", get(handlers::get_medication))
.route("/api/medications/:id", post(handlers::update_medication))
.route("/api/medications/:id/delete", post(handlers::delete_medication))
.route("/api/medications/:id/log", post(handlers::log_dose))
.route("/api/medications/:id/adherence", get(handlers::get_adherence))
// Health statistics management (Phase 2.7)
.route("/api/health-stats", post(handlers::create_health_stat))
.route("/api/health-stats", get(handlers::list_health_stats))
.route("/api/health-stats/trends", get(handlers::get_health_trends))
.route("/api/health-stats/:id", get(handlers::get_health_stat))
.route("/api/health-stats/:id", put(handlers::update_health_stat))
.route("/api/health-stats/:id", delete(handlers::delete_health_stat))
// Drug interactions (Phase 2.8)
.route("/api/interactions/check", post(handlers::check_interactions))
.route("/api/interactions/check-new", post(handlers::check_new_medication))
.layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::jwt_auth_middleware
));
let app = public_routes
.merge(protected_routes)
.with_state(state)
.layer(
ServiceBuilder::new()
// Add security headers first (applies to all responses)
.layer(axum::middleware::from_fn(
middleware::security_headers_middleware
))
// Add general rate limiting
.layer(axum::middleware::from_fn(
middleware::general_rate_limit_middleware
))
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive()),
);
let addr = format!("{}:{}", config.server.host, config.server.port);
eprintln!("Binding to {}...", addr);
let listener = tokio::net::TcpListener::bind(&addr).await?;
eprintln!("Server listening on {}", &addr);
tracing::info!("Server listening on {}", &addr);
axum::serve(listener, app).await?;
Ok(())
}