botserver/src/core/shared/analytics.rs

558 lines
17 KiB
Rust
Raw Normal View History

2025-11-22 22:55:35 -03:00
//! Analytics & Reporting Module
//!
//! Provides comprehensive analytics, reporting, and insights generation capabilities.
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Json,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use uuid::Uuid;
use crate::shared::state::AppState;
// ===== Request/Response Structures =====
#[derive(Debug, Deserialize)]
pub struct ReportQuery {
pub report_type: String,
pub start_date: Option<String>,
pub end_date: Option<String>,
pub group_by: Option<String>,
pub filters: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub struct ScheduleReportRequest {
pub report_type: String,
pub frequency: String,
pub recipients: Vec<String>,
pub format: String,
pub filters: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub struct MetricsCollectionRequest {
pub metric_type: String,
pub value: f64,
pub labels: Option<serde_json::Value>,
pub timestamp: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize)]
pub struct InsightsQuery {
pub data_source: String,
pub analysis_type: String,
pub time_range: String,
}
#[derive(Debug, Deserialize)]
pub struct TrendsQuery {
pub metric: String,
pub start_date: String,
pub end_date: String,
pub granularity: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ExportRequest {
pub data_type: String,
pub format: String,
pub filters: Option<serde_json::Value>,
}
#[derive(Debug, Serialize)]
pub struct DashboardResponse {
pub overview: OverviewStats,
pub recent_activity: Vec<ActivityItem>,
pub charts: Vec<ChartData>,
pub alerts: Vec<AlertItem>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct OverviewStats {
pub total_users: u32,
pub active_users: u32,
pub total_files: u64,
pub total_storage_gb: f64,
pub total_messages: u64,
pub total_calls: u32,
pub growth_rate: f64,
}
#[derive(Debug, Serialize)]
pub struct ActivityItem {
pub id: Uuid,
pub action: String,
pub user_id: Option<Uuid>,
pub user_name: String,
pub resource_type: String,
pub resource_id: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct ChartData {
pub chart_type: String,
pub title: String,
pub labels: Vec<String>,
pub datasets: Vec<DatasetInfo>,
}
#[derive(Debug, Serialize)]
pub struct DatasetInfo {
pub label: String,
pub data: Vec<f64>,
pub color: String,
}
#[derive(Debug, Serialize)]
pub struct AlertItem {
pub id: Uuid,
pub severity: String,
pub title: String,
pub message: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct ReportResponse {
pub id: Uuid,
pub report_type: String,
pub generated_at: DateTime<Utc>,
pub data: serde_json::Value,
pub summary: Option<String>,
pub download_url: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ScheduledReportResponse {
pub id: Uuid,
pub report_type: String,
pub frequency: String,
pub recipients: Vec<String>,
pub format: String,
pub next_run: DateTime<Utc>,
pub last_run: Option<DateTime<Utc>>,
pub status: String,
}
#[derive(Debug, Serialize)]
pub struct MetricResponse {
pub metric_type: String,
pub value: f64,
pub timestamp: DateTime<Utc>,
pub labels: serde_json::Value,
}
#[derive(Debug, Serialize)]
pub struct InsightsResponse {
pub insights: Vec<Insight>,
pub confidence_score: f64,
pub generated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct Insight {
pub title: String,
pub description: String,
pub insight_type: String,
pub severity: String,
pub data: serde_json::Value,
pub recommendations: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct TrendsResponse {
pub metric: String,
pub trend_direction: String,
pub change_percentage: f64,
pub data_points: Vec<TrendDataPoint>,
pub forecast: Option<Vec<TrendDataPoint>>,
}
#[derive(Debug, Serialize)]
pub struct TrendDataPoint {
pub timestamp: DateTime<Utc>,
pub value: f64,
}
#[derive(Debug, Serialize)]
pub struct ExportResponse {
pub export_id: Uuid,
pub format: String,
pub size_bytes: u64,
pub download_url: String,
pub expires_at: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct SuccessResponse {
pub success: bool,
pub message: Option<String>,
}
// ===== API Handlers =====
/// GET /analytics/dashboard - Get analytics dashboard
pub async fn get_dashboard(
State(state): State<Arc<AppState>>,
) -> Result<Json<DashboardResponse>, (StatusCode, Json<serde_json::Value>)> {
let now = Utc::now();
let dashboard = DashboardResponse {
overview: OverviewStats {
total_users: 1250,
active_users: 892,
total_files: 45678,
total_storage_gb: 234.5,
total_messages: 123456,
total_calls: 3456,
growth_rate: 12.5,
},
recent_activity: vec![
ActivityItem {
id: Uuid::new_v4(),
action: "file_upload".to_string(),
user_id: Some(Uuid::new_v4()),
user_name: "John Doe".to_string(),
resource_type: "file".to_string(),
resource_id: "document.pdf".to_string(),
timestamp: now,
},
ActivityItem {
id: Uuid::new_v4(),
action: "user_login".to_string(),
user_id: Some(Uuid::new_v4()),
user_name: "Jane Smith".to_string(),
resource_type: "session".to_string(),
resource_id: "session-123".to_string(),
timestamp: now,
},
],
charts: vec![
ChartData {
chart_type: "line".to_string(),
title: "Daily Active Users".to_string(),
labels: vec!["Mon".to_string(), "Tue".to_string(), "Wed".to_string(), "Thu".to_string(), "Fri".to_string()],
datasets: vec![DatasetInfo {
label: "Active Users".to_string(),
data: vec![850.0, 920.0, 880.0, 950.0, 892.0],
color: "#3b82f6".to_string(),
}],
},
ChartData {
chart_type: "bar".to_string(),
title: "Storage Usage".to_string(),
labels: vec!["Files".to_string(), "Media".to_string(), "Backups".to_string()],
datasets: vec![DatasetInfo {
label: "GB".to_string(),
data: vec![120.5, 80.3, 33.7],
color: "#10b981".to_string(),
}],
},
],
alerts: vec![
AlertItem {
id: Uuid::new_v4(),
severity: "warning".to_string(),
title: "Storage capacity".to_string(),
message: "Storage usage is at 78%".to_string(),
timestamp: now,
},
],
updated_at: now,
};
Ok(Json(dashboard))
}
/// POST /analytics/reports/generate - Generate analytics report
pub async fn generate_report(
State(state): State<Arc<AppState>>,
Query(params): Query<ReportQuery>,
) -> Result<Json<ReportResponse>, (StatusCode, Json<serde_json::Value>)> {
let report_id = Uuid::new_v4();
let now = Utc::now();
let report_data = match params.report_type.as_str() {
"user_activity" => {
serde_json::json!({
"total_users": 1250,
"active_users": 892,
"new_users_this_month": 45,
"user_engagement_score": 7.8,
"top_users": [
{"name": "John Doe", "activity_score": 95},
{"name": "Jane Smith", "activity_score": 88},
],
})
}
"storage" => {
serde_json::json!({
"total_storage_gb": 234.5,
"used_storage_gb": 182.3,
"available_storage_gb": 52.2,
"growth_rate_monthly": 8.5,
"largest_consumers": [
{"user": "John Doe", "storage_gb": 15.2},
{"user": "Jane Smith", "storage_gb": 12.8},
],
})
}
"communication" => {
serde_json::json!({
"total_messages": 123456,
"total_calls": 3456,
"average_call_duration_minutes": 23.5,
"most_active_channels": [
{"name": "General", "messages": 45678},
{"name": "Development", "messages": 23456},
],
})
}
_ => {
serde_json::json!({
"message": "Report data not available for this type"
})
}
};
let report = ReportResponse {
id: report_id,
report_type: params.report_type,
generated_at: now,
data: report_data,
summary: Some("Report generated successfully".to_string()),
download_url: Some(format!("/analytics/reports/{}/download", report_id)),
};
Ok(Json(report))
}
/// POST /analytics/reports/schedule - Schedule recurring report
pub async fn schedule_report(
State(state): State<Arc<AppState>>,
Json(req): Json<ScheduleReportRequest>,
) -> Result<Json<ScheduledReportResponse>, (StatusCode, Json<serde_json::Value>)> {
let schedule_id = Uuid::new_v4();
let now = Utc::now();
let next_run = match req.frequency.as_str() {
"daily" => now.checked_add_signed(chrono::Duration::days(1)).unwrap(),
"weekly" => now.checked_add_signed(chrono::Duration::weeks(1)).unwrap(),
"monthly" => now.checked_add_signed(chrono::Duration::days(30)).unwrap(),
_ => now.checked_add_signed(chrono::Duration::days(1)).unwrap(),
};
let scheduled = ScheduledReportResponse {
id: schedule_id,
report_type: req.report_type,
frequency: req.frequency,
recipients: req.recipients,
format: req.format,
next_run,
last_run: None,
status: "active".to_string(),
};
Ok(Json(scheduled))
}
/// POST /analytics/metrics/collect - Collect metric data
pub async fn collect_metrics(
State(state): State<Arc<AppState>>,
Json(req): Json<MetricsCollectionRequest>,
) -> Result<Json<MetricResponse>, (StatusCode, Json<serde_json::Value>)> {
let timestamp = req.timestamp.unwrap_or_else(Utc::now);
let metric = MetricResponse {
metric_type: req.metric_type,
value: req.value,
timestamp,
labels: req.labels.unwrap_or_else(|| serde_json::json!({})),
};
Ok(Json(metric))
}
/// POST /analytics/insights/generate - Generate insights from data
pub async fn generate_insights(
State(state): State<Arc<AppState>>,
Query(params): Query<InsightsQuery>,
) -> Result<Json<InsightsResponse>, (StatusCode, Json<serde_json::Value>)> {
let now = Utc::now();
let insights = match params.analysis_type.as_str() {
"performance" => {
vec![
Insight {
title: "High User Engagement".to_string(),
description: "User engagement has increased by 15% this week".to_string(),
insight_type: "positive".to_string(),
severity: "info".to_string(),
data: serde_json::json!({
"current_engagement": 7.8,
"previous_engagement": 6.8,
"change_percentage": 15.0
}),
recommendations: vec![
"Continue current engagement strategies".to_string(),
"Consider expanding successful features".to_string(),
],
},
Insight {
title: "Storage Optimization Needed".to_string(),
description: "Storage usage growing faster than expected".to_string(),
insight_type: "warning".to_string(),
severity: "medium".to_string(),
data: serde_json::json!({
"current_usage_gb": 182.3,
"projected_usage_gb": 250.0,
"days_until_full": 45
}),
recommendations: vec![
"Review and archive old files".to_string(),
"Implement storage quotas per user".to_string(),
"Consider upgrading storage capacity".to_string(),
],
},
]
}
"usage" => {
vec![
Insight {
title: "Peak Usage Times".to_string(),
description: "Highest activity between 9 AM - 11 AM".to_string(),
insight_type: "informational".to_string(),
severity: "info".to_string(),
data: serde_json::json!({
"peak_hours": ["09:00", "10:00", "11:00"],
"average_users": 750
}),
recommendations: vec![
"Schedule maintenance outside peak hours".to_string(),
"Ensure adequate resources during peak times".to_string(),
],
},
]
}
"security" => {
vec![
Insight {
title: "Failed Login Attempts".to_string(),
description: "Unusual number of failed login attempts detected".to_string(),
insight_type: "security".to_string(),
severity: "high".to_string(),
data: serde_json::json!({
"failed_attempts": 127,
"affected_accounts": 15,
"suspicious_ips": ["192.168.1.1", "10.0.0.5"]
}),
recommendations: vec![
"Enable two-factor authentication".to_string(),
"Review and block suspicious IP addresses".to_string(),
"Notify affected users".to_string(),
],
},
]
}
_ => vec![],
};
let response = InsightsResponse {
insights,
confidence_score: 0.85,
generated_at: now,
};
Ok(Json(response))
}
/// POST /analytics/trends/analyze - Analyze trends
pub async fn analyze_trends(
State(state): State<Arc<AppState>>,
Query(params): Query<TrendsQuery>,
) -> Result<Json<TrendsResponse>, (StatusCode, Json<serde_json::Value>)> {
let start_date = DateTime::parse_from_rfc3339(&params.start_date)
.unwrap_or_else(|_| {
Utc::now()
.checked_sub_signed(chrono::Duration::days(30))
.unwrap()
.into()
})
.with_timezone(&Utc);
let end_date = DateTime::parse_from_rfc3339(&params.end_date)
.unwrap_or_else(|_| Utc::now().into())
.with_timezone(&Utc);
let data_points = vec![
TrendDataPoint {
timestamp: start_date,
value: 850.0,
},
TrendDataPoint {
timestamp: start_date.checked_add_signed(chrono::Duration::days(5)).unwrap(),
value: 920.0,
},
TrendDataPoint {
timestamp: start_date.checked_add_signed(chrono::Duration::days(10)).unwrap(),
value: 880.0,
},
TrendDataPoint {
timestamp: start_date.checked_add_signed(chrono::Duration::days(15)).unwrap(),
value: 950.0,
},
TrendDataPoint {
timestamp: end_date,
value: 892.0,
},
];
let forecast = vec![
TrendDataPoint {
timestamp: end_date.checked_add_signed(chrono::Duration::days(5)).unwrap(),
value: 910.0,
},
TrendDataPoint {
timestamp: end_date.checked_add_signed(chrono::Duration::days(10)).unwrap(),
value: 935.0,
},
];
let trends = TrendsResponse {
metric: params.metric,
trend_direction: "upward".to_string(),
change_percentage: 4.9,
data_points,
forecast: Some(forecast),
};
Ok(Json(trends))
}
/// POST /analytics/export - Export analytics data
pub async fn export_analytics(
State(state): State<Arc<AppState>>,
Json(req): Json<ExportRequest>,
) -> Result<Json<ExportResponse>, (StatusCode, Json<serde_json::Value>)> {
let export_id = Uuid::new_v4();
let now = Utc::now();
let expires_at = now.checked_add_signed(chrono::Duration::hours(24)).unwrap();
let export = ExportResponse {
export_id,
format: req.format,
size_bytes: 1024 * 1024 * 5,
download_url: format!("/analytics/exports/{}/download", export_id),
expires_at,
};
Ok(Json(export))
}