- 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.
199 lines
7.2 KiB
Rust
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(())
|
|
}
|