use super::traits::{Service, ServiceError, ServiceResult, ValidationContext};
use crate::core::models::{common::DashboardId, Dashboard};
use crate::repository::{
dashboard::{DashboardFilterParams, DashboardRepository},
traits::PaginationParams,
};
use async_trait::async_trait;
use std::sync::Arc;
#[async_trait]
pub trait DashboardService: Service {
async fn get_dashboard(&self, id: DashboardId) -> ServiceResult<Dashboard>;
async fn list_dashboards(
&self,
pagination: Option<PaginationParams>,
filters: Option<DashboardFilterParams>,
) -> ServiceResult<Vec<Dashboard>>;
async fn create_dashboard(&self, dashboard: Dashboard) -> ServiceResult<Dashboard>;
async fn update_dashboard(
&self,
id: DashboardId,
dashboard: Dashboard,
) -> ServiceResult<Dashboard>;
async fn delete_dashboard(&self, id: DashboardId) -> ServiceResult<()>;
async fn archive_dashboard(&self, id: DashboardId) -> ServiceResult<()>;
async fn unarchive_dashboard(&self, id: DashboardId) -> ServiceResult<()>;
async fn duplicate_dashboard(
&self,
id: DashboardId,
new_name: &str,
) -> ServiceResult<Dashboard>;
async fn add_card_to_dashboard(
&self,
dashboard_id: DashboardId,
card_data: &serde_json::Value,
) -> ServiceResult<serde_json::Value>;
async fn remove_card_from_dashboard(
&self,
dashboard_id: DashboardId,
card_id: i32,
) -> ServiceResult<()>;
async fn validate_dashboard(&self, dashboard: &Dashboard) -> ServiceResult<()>;
}
pub struct HttpDashboardService {
repository: Arc<dyn DashboardRepository>,
}
impl HttpDashboardService {
pub fn new(repository: Arc<dyn DashboardRepository>) -> Self {
Self { repository }
}
fn validate_dashboard_rules(&self, dashboard: &Dashboard) -> ServiceResult<()> {
let mut context = ValidationContext::new();
if dashboard.name.trim().is_empty() {
context.add_error("Dashboard name cannot be empty");
}
if dashboard.name.len() > 255 {
context.add_error("Dashboard name cannot exceed 255 characters");
}
if let Some(desc) = &dashboard.description {
if desc.len() > 5000 {
context.add_error("Dashboard description cannot exceed 5000 characters");
}
}
if let Some(ttl) = dashboard.cache_ttl {
if ttl < 0 {
context.add_error("Cache TTL cannot be negative");
}
if ttl > 86400 {
context.add_error("Cache TTL cannot exceed 24 hours");
}
}
context.to_result()
}
}
#[async_trait]
impl Service for HttpDashboardService {
fn name(&self) -> &str {
"DashboardService"
}
}
#[async_trait]
impl DashboardService for HttpDashboardService {
async fn get_dashboard(&self, id: DashboardId) -> ServiceResult<Dashboard> {
self.repository.get(&id).await.map_err(ServiceError::from)
}
async fn list_dashboards(
&self,
pagination: Option<PaginationParams>,
filters: Option<DashboardFilterParams>,
) -> ServiceResult<Vec<Dashboard>> {
self.repository
.list_with_filters(pagination, filters)
.await
.map_err(ServiceError::from)
}
async fn create_dashboard(&self, dashboard: Dashboard) -> ServiceResult<Dashboard> {
self.validate_dashboard_rules(&dashboard)?;
self.repository
.create(&dashboard)
.await
.map_err(ServiceError::from)
}
async fn update_dashboard(
&self,
id: DashboardId,
mut dashboard: Dashboard,
) -> ServiceResult<Dashboard> {
dashboard.id = Some(id);
self.validate_dashboard_rules(&dashboard)?;
self.repository.get(&id).await.map_err(ServiceError::from)?;
self.repository
.update(&id, &dashboard)
.await
.map_err(ServiceError::from)
}
async fn delete_dashboard(&self, id: DashboardId) -> ServiceResult<()> {
self.repository.get(&id).await.map_err(ServiceError::from)?;
self.repository
.delete(&id)
.await
.map_err(ServiceError::from)
}
async fn archive_dashboard(&self, id: DashboardId) -> ServiceResult<()> {
self.repository
.archive(&id)
.await
.map_err(ServiceError::from)
}
async fn unarchive_dashboard(&self, id: DashboardId) -> ServiceResult<()> {
self.repository
.unarchive(&id)
.await
.map_err(ServiceError::from)
}
async fn duplicate_dashboard(
&self,
id: DashboardId,
new_name: &str,
) -> ServiceResult<Dashboard> {
if new_name.trim().is_empty() {
return Err(ServiceError::Validation(
"New dashboard name cannot be empty".to_string(),
));
}
self.repository
.duplicate(&id, new_name)
.await
.map_err(ServiceError::from)
}
async fn add_card_to_dashboard(
&self,
dashboard_id: DashboardId,
card_data: &serde_json::Value,
) -> ServiceResult<serde_json::Value> {
if card_data.is_null() {
return Err(ServiceError::Validation(
"Card data cannot be null".to_string(),
));
}
self.repository
.add_card(&dashboard_id, card_data)
.await
.map_err(ServiceError::from)
}
async fn remove_card_from_dashboard(
&self,
dashboard_id: DashboardId,
card_id: i32,
) -> ServiceResult<()> {
self.repository
.remove_card(&dashboard_id, card_id)
.await
.map_err(ServiceError::from)
}
async fn validate_dashboard(&self, dashboard: &Dashboard) -> ServiceResult<()> {
self.validate_dashboard_rules(dashboard)
}
}