use axum::{ extract::{Path, Query, State}, response::IntoResponse, routing::{delete, get, post, put}, Json, Router, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; use crate::shared::state::AppState; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Objective { pub id: Uuid, pub organization_id: Uuid, pub owner_id: Uuid, pub parent_id: Option, pub title: String, pub description: String, pub period: String, pub status: ObjectiveStatus, pub progress: f32, pub visibility: Visibility, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ObjectiveStatus { Draft, Active, OnTrack, AtRisk, Behind, Completed, Cancelled, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum Visibility { Private, Team, Organization, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KeyResult { pub id: Uuid, pub objective_id: Uuid, pub owner_id: Uuid, pub title: String, pub metric_type: MetricType, pub start_value: f64, pub target_value: f64, pub current_value: f64, pub weight: f32, pub due_date: Option>, pub status: KRStatus, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum MetricType { Percentage, Number, Currency, Boolean, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum KRStatus { NotStarted, InProgress, AtRisk, Completed, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CheckIn { pub id: Uuid, pub key_result_id: Uuid, pub user_id: Uuid, pub previous_value: f64, pub new_value: f64, pub note: String, pub confidence: Confidence, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum Confidence { Low, Medium, High, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoalTemplate { pub id: Uuid, pub organization_id: Option, pub name: String, pub description: String, pub category: String, pub objective_template: ObjectiveTemplate, pub key_result_templates: Vec, pub is_system: bool, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ObjectiveTemplate { pub title: String, pub description: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KeyResultTemplate { pub title: String, pub metric_type: MetricType, pub suggested_target: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AlignmentNode { pub objective: Objective, pub key_results: Vec, pub children: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoalsDashboard { pub total_objectives: i32, pub completed_objectives: i32, pub at_risk_objectives: i32, pub average_progress: f32, pub upcoming_check_ins: Vec, pub recent_activity: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpcomingCheckIn { pub key_result_id: Uuid, pub key_result_title: String, pub objective_title: String, pub due_date: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoalActivity { pub id: Uuid, pub activity_type: GoalActivityType, pub user_id: Uuid, pub user_name: String, pub objective_id: Option, pub key_result_id: Option, pub description: String, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum GoalActivityType { ObjectiveCreated, ObjectiveUpdated, ObjectiveCompleted, KeyResultCreated, KeyResultUpdated, CheckInRecorded, ProgressChanged, } #[derive(Debug, Deserialize)] pub struct ListObjectivesQuery { pub owner_id: Option, pub status: Option, pub period: Option, pub parent_id: Option, pub limit: Option, pub offset: Option, } #[derive(Debug, Deserialize)] pub struct CreateObjectiveRequest { pub title: String, pub description: Option, pub period: String, pub parent_id: Option, pub visibility: Option, } #[derive(Debug, Deserialize)] pub struct UpdateObjectiveRequest { pub title: Option, pub description: Option, pub status: Option, pub visibility: Option, } #[derive(Debug, Deserialize)] pub struct CreateKeyResultRequest { pub title: String, pub metric_type: MetricType, pub start_value: Option, pub target_value: f64, pub weight: Option, pub due_date: Option>, } #[derive(Debug, Deserialize)] pub struct UpdateKeyResultRequest { pub title: Option, pub target_value: Option, pub current_value: Option, pub weight: Option, pub due_date: Option>, pub status: Option, } #[derive(Debug, Deserialize)] pub struct CreateCheckInRequest { pub new_value: f64, pub note: Option, pub confidence: Option, } #[derive(Debug, Deserialize)] pub struct AISuggestRequest { pub context: Option, pub role: Option, pub department: Option, } #[derive(Debug, Serialize)] pub struct AISuggestion { pub objective: ObjectiveTemplate, pub key_results: Vec, pub rationale: String, } #[derive(Debug, Clone)] pub struct GoalsService {} impl GoalsService { pub fn new() -> Self { Self {} } pub async fn list_objectives( &self, _organization_id: Uuid, _query: &ListObjectivesQuery, ) -> Result, GoalsError> { Ok(vec![]) } pub async fn create_objective( &self, organization_id: Uuid, owner_id: Uuid, req: CreateObjectiveRequest, ) -> Result { let now = Utc::now(); Ok(Objective { id: Uuid::new_v4(), organization_id, owner_id, parent_id: req.parent_id, title: req.title, description: req.description.unwrap_or_default(), period: req.period, status: ObjectiveStatus::Draft, progress: 0.0, visibility: req.visibility.unwrap_or(Visibility::Team), created_at: now, updated_at: now, }) } pub async fn get_objective( &self, _organization_id: Uuid, _objective_id: Uuid, ) -> Result, GoalsError> { Ok(None) } pub async fn update_objective( &self, _organization_id: Uuid, _objective_id: Uuid, _req: UpdateObjectiveRequest, ) -> Result { Err(GoalsError::NotFound("Objective not found".to_string())) } pub async fn delete_objective( &self, _organization_id: Uuid, _objective_id: Uuid, ) -> Result<(), GoalsError> { Ok(()) } pub async fn list_key_results( &self, _organization_id: Uuid, _objective_id: Uuid, ) -> Result, GoalsError> { Ok(vec![]) } pub async fn create_key_result( &self, _organization_id: Uuid, objective_id: Uuid, owner_id: Uuid, req: CreateKeyResultRequest, ) -> Result { let now = Utc::now(); Ok(KeyResult { id: Uuid::new_v4(), objective_id, owner_id, title: req.title, metric_type: req.metric_type, start_value: req.start_value.unwrap_or(0.0), target_value: req.target_value, current_value: req.start_value.unwrap_or(0.0), weight: req.weight.unwrap_or(1.0), due_date: req.due_date, status: KRStatus::NotStarted, created_at: now, updated_at: now, }) } pub async fn update_key_result( &self, _organization_id: Uuid, _key_result_id: Uuid, _req: UpdateKeyResultRequest, ) -> Result { Err(GoalsError::NotFound("Key result not found".to_string())) } pub async fn delete_key_result( &self, _organization_id: Uuid, _key_result_id: Uuid, ) -> Result<(), GoalsError> { Ok(()) } pub async fn create_check_in( &self, _organization_id: Uuid, key_result_id: Uuid, user_id: Uuid, req: CreateCheckInRequest, ) -> Result { Ok(CheckIn { id: Uuid::new_v4(), key_result_id, user_id, previous_value: 0.0, new_value: req.new_value, note: req.note.unwrap_or_default(), confidence: req.confidence.unwrap_or(Confidence::Medium), created_at: Utc::now(), }) } pub async fn get_check_in_history( &self, _organization_id: Uuid, _key_result_id: Uuid, ) -> Result, GoalsError> { Ok(vec![]) } pub async fn get_dashboard( &self, _organization_id: Uuid, _user_id: Uuid, ) -> Result { Ok(GoalsDashboard { total_objectives: 0, completed_objectives: 0, at_risk_objectives: 0, average_progress: 0.0, upcoming_check_ins: vec![], recent_activity: vec![], }) } pub async fn get_alignment_tree( &self, _organization_id: Uuid, ) -> Result, GoalsError> { Ok(vec![]) } pub async fn suggest_goals( &self, _organization_id: Uuid, _req: AISuggestRequest, ) -> Result, GoalsError> { Ok(vec![ AISuggestion { objective: ObjectiveTemplate { title: "Improve customer satisfaction".to_string(), description: "Enhance customer experience across all touchpoints".to_string(), }, key_results: vec![ KeyResultTemplate { title: "Increase NPS score".to_string(), metric_type: MetricType::Number, suggested_target: Some(50.0), }, KeyResultTemplate { title: "Reduce support ticket resolution time".to_string(), metric_type: MetricType::Number, suggested_target: Some(24.0), }, ], rationale: "Customer satisfaction directly impacts retention and growth".to_string(), }, ]) } pub async fn get_templates( &self, _organization_id: Uuid, ) -> Result, GoalsError> { Ok(vec![]) } fn calculate_objective_progress(&self, key_results: &[KeyResult]) -> f32 { if key_results.is_empty() { return 0.0; } let total_weight: f32 = key_results.iter().map(|kr| kr.weight).sum(); if total_weight == 0.0 { return 0.0; } key_results .iter() .map(|kr| { let range = kr.target_value - kr.start_value; let progress = if range == 0.0 { 1.0 } else { ((kr.current_value - kr.start_value) / range).clamp(0.0, 1.0) }; progress as f32 * kr.weight }) .sum::() / total_weight } } impl Default for GoalsService { fn default() -> Self { Self::new() } } #[derive(Debug, thiserror::Error)] pub enum GoalsError { #[error("Not found: {0}")] NotFound(String), #[error("Unauthorized: {0}")] Unauthorized(String), #[error("Validation error: {0}")] Validation(String), #[error("Database error: {0}")] Database(String), } impl IntoResponse for GoalsError { fn into_response(self) -> axum::response::Response { use axum::http::StatusCode; let (status, message) = match &self { Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()), Self::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()), Self::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()), Self::Database(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()), }; (status, Json(serde_json::json!({ "error": message }))).into_response() } } pub async fn handle_list_objectives( State(_state): State>, Query(query): Query, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let objectives = service.list_objectives(org_id, &query).await?; Ok(Json(objectives)) } pub async fn handle_create_objective( State(_state): State>, Json(req): Json, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let user_id = Uuid::nil(); let objective = service.create_objective(org_id, user_id, req).await?; Ok(Json(objective)) } pub async fn handle_get_objective( State(_state): State>, Path(objective_id): Path, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let objective = service.get_objective(org_id, objective_id).await?; Ok(Json(objective)) } pub async fn handle_update_objective( State(_state): State>, Path(objective_id): Path, Json(req): Json, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let objective = service.update_objective(org_id, objective_id, req).await?; Ok(Json(objective)) } pub async fn handle_delete_objective( State(_state): State>, Path(objective_id): Path, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); service.delete_objective(org_id, objective_id).await?; Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_list_key_results( State(_state): State>, Path(objective_id): Path, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let key_results = service.list_key_results(org_id, objective_id).await?; Ok(Json(key_results)) } pub async fn handle_create_key_result( State(_state): State>, Path(objective_id): Path, Json(req): Json, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let user_id = Uuid::nil(); let key_result = service .create_key_result(org_id, objective_id, user_id, req) .await?; Ok(Json(key_result)) } pub async fn handle_update_key_result( State(_state): State>, Path(key_result_id): Path, Json(req): Json, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let key_result = service.update_key_result(org_id, key_result_id, req).await?; Ok(Json(key_result)) } pub async fn handle_delete_key_result( State(_state): State>, Path(key_result_id): Path, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); service.delete_key_result(org_id, key_result_id).await?; Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_create_check_in( State(_state): State>, Path(key_result_id): Path, Json(req): Json, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let user_id = Uuid::nil(); let check_in = service .create_check_in(org_id, key_result_id, user_id, req) .await?; Ok(Json(check_in)) } pub async fn handle_get_check_in_history( State(_state): State>, Path(key_result_id): Path, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let history = service.get_check_in_history(org_id, key_result_id).await?; Ok(Json(history)) } pub async fn handle_get_dashboard( State(_state): State>, ) -> Result, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let user_id = Uuid::nil(); let dashboard = service.get_dashboard(org_id, user_id).await?; Ok(Json(dashboard)) } pub async fn handle_get_alignment( State(_state): State>, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let tree = service.get_alignment_tree(org_id).await?; Ok(Json(tree)) } pub async fn handle_ai_suggest( State(_state): State>, Json(req): Json, ) -> Result>, GoalsError> { let service = GoalsService::new(); let org_id = Uuid::nil(); let suggestions = service.suggest_goals(org_id, req).await?; Ok(Json(suggestions)) } pub fn configure_goals_routes() -> Router> { Router::new() .route("/api/goals/objectives", get(handle_list_objectives)) .route("/api/goals/objectives", post(handle_create_objective)) .route("/api/goals/objectives/:id", get(handle_get_objective)) .route("/api/goals/objectives/:id", put(handle_update_objective)) .route("/api/goals/objectives/:id", delete(handle_delete_objective)) .route( "/api/goals/objectives/:id/key-results", get(handle_list_key_results), ) .route( "/api/goals/objectives/:id/key-results", post(handle_create_key_result), ) .route("/api/goals/key-results/:id", put(handle_update_key_result)) .route( "/api/goals/key-results/:id", delete(handle_delete_key_result), ) .route( "/api/goals/key-results/:id/check-in", post(handle_create_check_in), ) .route( "/api/goals/key-results/:id/history", get(handle_get_check_in_history), ) .route("/api/goals/dashboard", get(handle_get_dashboard)) .route("/api/goals/alignment", get(handle_get_alignment)) .route("/api/goals/ai/suggest", post(handle_ai_suggest)) }