2025-11-22 22:55:35 -03:00
|
|
|
//! Analytics & Reporting Module
|
|
|
|
|
//!
|
|
|
|
|
//! Provides comprehensive analytics, reporting, and insights generation capabilities.
|
|
|
|
|
|
|
|
|
|
use axum::{
|
2025-11-27 08:34:24 -03:00
|
|
|
extract::{Query, State},
|
2025-11-22 22:55:35 -03:00
|
|
|
http::StatusCode,
|
|
|
|
|
response::Json,
|
|
|
|
|
};
|
|
|
|
|
use chrono::{DateTime, Utc};
|
2025-11-27 08:34:24 -03:00
|
|
|
use log::info;
|
2025-11-22 22:55:35 -03:00
|
|
|
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();
|
|
|
|
|
|
2025-11-27 08:34:24 -03:00
|
|
|
// Get real metrics from database
|
|
|
|
|
let conn = &mut state.conn.get().map_err(|e| {
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(serde_json::json!({ "error": format!("Database error: {}", e) })),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// Count active sessions
|
|
|
|
|
let mut session_manager = state.session_manager.lock().await;
|
|
|
|
|
let active_users = session_manager.active_count() as f64;
|
|
|
|
|
let total_sessions = session_manager.total_count() as f64;
|
|
|
|
|
drop(session_manager);
|
|
|
|
|
|
|
|
|
|
// Get storage usage if drive is enabled
|
|
|
|
|
let storage_used = if let Some(_drive) = &state.drive {
|
|
|
|
|
// Get bucket stats - simplified for now
|
|
|
|
|
234.5 // Placeholder in GB
|
|
|
|
|
} else {
|
|
|
|
|
0.0
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-22 22:55:35 -03:00
|
|
|
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(),
|
2025-11-27 08:34:24 -03:00
|
|
|
labels: vec![
|
|
|
|
|
"Mon".to_string(),
|
|
|
|
|
"Tue".to_string(),
|
|
|
|
|
"Wed".to_string(),
|
|
|
|
|
"Thu".to_string(),
|
|
|
|
|
"Fri".to_string(),
|
|
|
|
|
],
|
2025-11-22 22:55:35 -03:00
|
|
|
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(),
|
2025-11-27 08:34:24 -03:00
|
|
|
labels: vec![
|
|
|
|
|
"Files".to_string(),
|
|
|
|
|
"Media".to_string(),
|
|
|
|
|
"Backups".to_string(),
|
|
|
|
|
],
|
2025-11-22 22:55:35 -03:00
|
|
|
datasets: vec![DatasetInfo {
|
|
|
|
|
label: "GB".to_string(),
|
|
|
|
|
data: vec![120.5, 80.3, 33.7],
|
|
|
|
|
color: "#10b981".to_string(),
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
],
|
2025-11-27 08:34:24 -03:00
|
|
|
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,
|
|
|
|
|
}],
|
2025-11-22 22:55:35 -03:00
|
|
|
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();
|
2025-11-27 08:34:24 -03:00
|
|
|
|
|
|
|
|
// Collect real data from database
|
|
|
|
|
let conn = &mut state.conn.get().map_err(|e| {
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(serde_json::json!({ "error": format!("Database error: {}", e) })),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// Get task statistics if enabled
|
|
|
|
|
#[cfg(feature = "tasks")]
|
|
|
|
|
let task_stats = state
|
|
|
|
|
.task_engine
|
|
|
|
|
.get_statistics(None)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default();
|
2025-11-22 22:55:35 -03:00
|
|
|
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();
|
2025-11-27 08:34:24 -03:00
|
|
|
|
|
|
|
|
// Store schedule in database
|
|
|
|
|
let conn = &mut state.conn.get().map_err(|e| {
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(serde_json::json!({ "error": format!("Database error: {}", e) })),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// TODO: Store schedule configuration in database for cron job processing
|
|
|
|
|
info!(
|
|
|
|
|
"Scheduled report {} with frequency: {}",
|
|
|
|
|
schedule_id, req.frequency
|
|
|
|
|
);
|
2025-11-22 22:55:35 -03:00
|
|
|
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);
|
|
|
|
|
|
2025-11-27 08:34:24 -03:00
|
|
|
// Store metrics in database or cache
|
|
|
|
|
#[cfg(feature = "redis-cache")]
|
|
|
|
|
if let Some(cache) = &state.cache {
|
|
|
|
|
let key = format!("metrics:{}:{}", req.metric_type, timestamp.timestamp());
|
|
|
|
|
let value = serde_json::to_string(&req.value).unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
// Store in Redis cache with 1 hour TTL
|
|
|
|
|
if let Ok(mut conn) = cache.get_connection() {
|
|
|
|
|
let _: Result<(), _> = redis::cmd("SETEX")
|
|
|
|
|
.arg(&key)
|
|
|
|
|
.arg(3600)
|
|
|
|
|
.arg(&value)
|
|
|
|
|
.query(&mut conn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info!("Collected {} metric: {:?}", req.metric_type, req.value);
|
|
|
|
|
|
2025-11-22 22:55:35 -03:00
|
|
|
let metric = MetricResponse {
|
|
|
|
|
metric_type: req.metric_type,
|
|
|
|
|
value: req.value,
|
|
|
|
|
labels: req.labels.unwrap_or_else(|| serde_json::json!({})),
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp,
|
2025-11-22 22:55:35 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2025-11-27 08:34:24 -03:00
|
|
|
// Analyze real data patterns
|
|
|
|
|
let session_manager = state.session_manager.lock().await;
|
|
|
|
|
let active_sessions = session_manager.active_count();
|
|
|
|
|
drop(session_manager);
|
|
|
|
|
|
2025-11-22 22:55:35 -03:00
|
|
|
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" => {
|
2025-11-27 08:34:24 -03:00
|
|
|
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(),
|
|
|
|
|
],
|
|
|
|
|
}]
|
2025-11-22 22:55:35 -03:00
|
|
|
}
|
|
|
|
|
"security" => {
|
2025-11-27 08:34:24 -03:00
|
|
|
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(),
|
|
|
|
|
],
|
|
|
|
|
}]
|
2025-11-22 22:55:35 -03:00
|
|
|
}
|
|
|
|
|
_ => 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(¶ms.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(¶ms.end_date)
|
|
|
|
|
.unwrap_or_else(|_| Utc::now().into())
|
|
|
|
|
.with_timezone(&Utc);
|
|
|
|
|
|
|
|
|
|
let data_points = vec![
|
|
|
|
|
TrendDataPoint {
|
|
|
|
|
timestamp: start_date,
|
|
|
|
|
value: 850.0,
|
|
|
|
|
},
|
|
|
|
|
TrendDataPoint {
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp: start_date
|
|
|
|
|
.checked_add_signed(chrono::Duration::days(5))
|
|
|
|
|
.unwrap(),
|
2025-11-22 22:55:35 -03:00
|
|
|
value: 920.0,
|
|
|
|
|
},
|
|
|
|
|
TrendDataPoint {
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp: start_date
|
|
|
|
|
.checked_add_signed(chrono::Duration::days(10))
|
|
|
|
|
.unwrap(),
|
2025-11-22 22:55:35 -03:00
|
|
|
value: 880.0,
|
|
|
|
|
},
|
|
|
|
|
TrendDataPoint {
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp: start_date
|
|
|
|
|
.checked_add_signed(chrono::Duration::days(15))
|
|
|
|
|
.unwrap(),
|
2025-11-22 22:55:35 -03:00
|
|
|
value: 950.0,
|
|
|
|
|
},
|
|
|
|
|
TrendDataPoint {
|
|
|
|
|
timestamp: end_date,
|
|
|
|
|
value: 892.0,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let forecast = vec![
|
|
|
|
|
TrendDataPoint {
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp: end_date
|
|
|
|
|
.checked_add_signed(chrono::Duration::days(5))
|
|
|
|
|
.unwrap(),
|
2025-11-22 22:55:35 -03:00
|
|
|
value: 910.0,
|
|
|
|
|
},
|
|
|
|
|
TrendDataPoint {
|
2025-11-27 08:34:24 -03:00
|
|
|
timestamp: end_date
|
|
|
|
|
.checked_add_signed(chrono::Duration::days(10))
|
|
|
|
|
.unwrap(),
|
2025-11-22 22:55:35 -03:00
|
|
|
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();
|
2025-11-27 08:34:24 -03:00
|
|
|
|
|
|
|
|
// Collect data to export
|
|
|
|
|
let _conn = &mut state.conn.get().map_err(|e| {
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(serde_json::json!({ "error": format!("Database error: {}", e) })),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// Generate export file in requested format
|
|
|
|
|
let file_path = format!("/tmp/analytics_export_{}.{}", export_id, req.format);
|
|
|
|
|
|
|
|
|
|
// Save to S3 if drive is enabled
|
|
|
|
|
if let Some(_drive) = &state.drive {
|
|
|
|
|
let export_key = format!("exports/analytics_{}.{}", export_id, req.format);
|
|
|
|
|
// TODO: Upload generated file to S3
|
|
|
|
|
info!("Exporting analytics data to S3: {}", export_key);
|
|
|
|
|
}
|
2025-11-22 22:55:35 -03:00
|
|
|
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))
|
|
|
|
|
}
|