- Add HealthStatistics model with 10 stat types - Implement HealthStatisticsRepository - Create 6 health stats API endpoints - Add trend analysis with summary calculations - Follow medication repository pattern Status: 60% complete, needs compilation fixes
197 lines
7.3 KiB
Rust
197 lines
7.3 KiB
Rust
mod config;
|
|
mod db;
|
|
mod models;
|
|
mod auth;
|
|
mod handlers;
|
|
mod middleware;
|
|
mod security;
|
|
|
|
use axum::{
|
|
routing::{get, post, put, delete},
|
|
Router,
|
|
};
|
|
use tower::ServiceBuilder;
|
|
use tower_http::{
|
|
cors::CorsLayer,
|
|
trace::TraceLayer,
|
|
};
|
|
use config::Config;
|
|
|
|
#[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()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "normogen_backend=debug,tower_http=debug,axum=debug".into())
|
|
)
|
|
.init();
|
|
|
|
eprintln!("Loading configuration...");
|
|
let config = match Config::from_env() {
|
|
Ok(cfg) => {
|
|
tracing::info!("Configuration loaded successfully");
|
|
eprintln!("Config loaded: DB={}, Port={}", cfg.database.database, cfg.server.port);
|
|
cfg
|
|
}
|
|
Err(e) => {
|
|
eprintln!("FATAL: Failed to load configuration: {}", e);
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
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();
|
|
let mongo_client = database.client().clone();
|
|
|
|
// 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 Collection pattern
|
|
let health_stats_collection = database.collection("health_statistics");
|
|
let health_stats_repo = models::health_stats::HealthStatisticsRepository::new(
|
|
health_stats_collection
|
|
);
|
|
|
|
// 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: Some(mongo_client),
|
|
};
|
|
|
|
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))
|
|
.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(())
|
|
}
|