use crate::AppState; use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, Json, Router, }; use db::models::config::{ ConfigRepository, CreateDashboardConfigPayload, CreateOnboardingConfigPayload, CreateRuntimeConfigPayload, }; use db::models::user::UserRepository; use serde::Deserialize; use uuid::Uuid; pub fn onboarding_router() -> Router { Router::new() .route("/", get(list_onboarding_configs).post(create_onboarding_config)) .route("/{role_id}", get(get_active_onboarding_config)) .route("/by-key/{role_key}", get(get_onboarding_config_by_key)) } pub fn dashboard_router() -> Router { Router::new() .route("/", get(list_dashboard_configs).post(create_dashboard_config)) .route("/{role_id}", get(get_active_dashboard_config)) .route("/by-key/{role_key}", get(get_dashboard_config_by_key)) } pub fn runtime_router() -> Router { Router::new() .route("/", get(get_my_runtime_config).post(create_runtime_config)) .route("/{role_id}", get(get_active_runtime_config)) } async fn get_my_runtime_config( auth: contracts::auth_middleware::AuthUser, State(state): State, ) -> Result { let role_key = auth.claims.active_role.clone(); let config = ConfigRepository::get_active_runtime_by_role_key(&state.pool, &role_key) .await .map_err(|e| match e { sqlx::Error::RowNotFound => ( StatusCode::NOT_FOUND, format!("No runtime config found for role {}", role_key), ), e => (StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e)), })?; // Fetch live user data to merge into the response so the `user` field is always fresh. let user = UserRepository::get_by_id(&state.pool, auth.user_id) .await .map_err(|_| (StatusCode::UNAUTHORIZED, "User not found".to_string()))?; let roles = UserRepository::get_user_role_keys(&state.pool, auth.user_id) .await .unwrap_or_default(); // Merge the stored config_json with live user data. // `config_json` holds role/modules/flags/permissions; we add the `user` sub-object. let mut response = config.config_json.clone(); if let Some(obj) = response.as_object_mut() { obj.insert( "user".to_string(), serde_json::json!({ "id": user.id.to_string(), "full_name": user.full_name.unwrap_or_default(), "email": user.email, "roles": roles, "active_role": role_key, }), ); // Ensure role is always set from the JWT active_role (authoritative) obj.insert("role".to_string(), serde_json::Value::String(role_key)); } Ok((StatusCode::OK, Json(response))) } async fn create_onboarding_config( State(state): State, Json(payload): Json, ) -> Result { match ConfigRepository::create_onboarding_config(&state.pool, payload).await { Ok(config) => Ok((StatusCode::CREATED, Json(config))), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn get_active_onboarding_config( State(state): State, Path(role_id): Path, ) -> Result { match ConfigRepository::get_active_onboarding_config(&state.pool, role_id).await { Ok(config) => Ok((StatusCode::OK, Json(config))), Err(sqlx::Error::RowNotFound) => Err(( StatusCode::NOT_FOUND, "Active onboarding config not found".to_string(), )), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn list_onboarding_configs( State(state): State, ) -> Result { match ConfigRepository::get_all_onboarding_configs(&state.pool).await { Ok(configs) => Ok((StatusCode::OK, Json(configs))), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn create_dashboard_config( State(state): State, Json(payload): Json, ) -> Result { match ConfigRepository::create_dashboard_config(&state.pool, payload).await { Ok(config) => Ok((StatusCode::CREATED, Json(config))), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } #[derive(Deserialize)] struct DashboardQuery { audience: String, } async fn get_active_dashboard_config( State(state): State, Path(role_id): Path, Query(query): Query, ) -> Result { match ConfigRepository::get_active_dashboard_config(&state.pool, role_id, &query.audience).await { Ok(config) => Ok((StatusCode::OK, Json(config))), Err(sqlx::Error::RowNotFound) => Err(( StatusCode::NOT_FOUND, "Active dashboard config not found".to_string(), )), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn list_dashboard_configs( State(state): State, ) -> Result { match ConfigRepository::get_all_dashboard_configs(&state.pool).await { Ok(configs) => Ok((StatusCode::OK, Json(configs))), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn create_runtime_config( State(state): State, Json(payload): Json, ) -> Result { match ConfigRepository::create_runtime_config(&state.pool, payload).await { Ok(config) => Ok((StatusCode::CREATED, Json(config))), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn get_active_runtime_config( State(state): State, Path(role_id): Path, ) -> Result { match ConfigRepository::get_active_runtime_config(&state.pool, role_id).await { Ok(config) => Ok((StatusCode::OK, Json(config))), Err(sqlx::Error::RowNotFound) => Err(( StatusCode::NOT_FOUND, "Active runtime config not found".to_string(), )), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn get_onboarding_config_by_key( State(state): State, Path(role_key): Path, ) -> Result { match ConfigRepository::get_active_onboarding_by_role_key(&state.pool, &role_key).await { Ok(config) => Ok((StatusCode::OK, Json(config))), Err(sqlx::Error::RowNotFound) => Err(( StatusCode::NOT_FOUND, format!("Active onboarding config for role '{}' not found", role_key), )), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } } async fn get_dashboard_config_by_key( State(state): State, Path(role_key): Path, Query(query): Query, ) -> Result { match ConfigRepository::get_active_dashboard_by_role_key(&state.pool, &role_key, &query.audience).await { Ok(config) => Ok((StatusCode::OK, Json(config))), Err(sqlx::Error::RowNotFound) => Err(( StatusCode::NOT_FOUND, format!("Active dashboard config for role '{}' not found", role_key), )), Err(e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("Database error: {}", e), )), } }