2025-03-20 11:04:14 +08:00
|
|
|
use anyhow::{anyhow, Result};
|
|
|
|
use chrono::Utc;
|
|
|
|
use database::pool::get_pg_pool;
|
|
|
|
use database::schema::dashboard_files;
|
|
|
|
use diesel::{ExpressionMethods, QueryDsl};
|
|
|
|
use diesel_async::RunQueryDsl;
|
2025-03-20 12:11:20 +08:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-03-20 11:04:14 +08:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2025-03-20 12:11:20 +08:00
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
pub struct DeleteDashboardsRequest {
|
|
|
|
pub ids: Vec<Uuid>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
pub struct DeleteDashboardsResponse {
|
|
|
|
pub success: bool,
|
|
|
|
pub deleted_count: usize,
|
|
|
|
pub message: String,
|
|
|
|
pub failed_ids: Vec<Uuid>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handles the deletion of multiple dashboards by IDs
|
|
|
|
///
|
|
|
|
/// Takes a list of dashboard IDs and performs a soft delete operation on each one
|
|
|
|
/// Returns information about successful and failed operations
|
|
|
|
pub async fn delete_dashboards_handler(
|
|
|
|
request: DeleteDashboardsRequest,
|
|
|
|
user_id: &Uuid
|
|
|
|
) -> Result<DeleteDashboardsResponse> {
|
|
|
|
let mut failed_ids = Vec::new();
|
|
|
|
|
|
|
|
// We need to track both the dashboard ID and the result of the deletion operation
|
|
|
|
let mut operations = Vec::new();
|
|
|
|
for &id in &request.ids {
|
|
|
|
operations.push((id, delete_single_dashboard(id, user_id)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute each operation and track failures
|
|
|
|
for (id, operation) in operations {
|
2025-03-22 01:26:57 +08:00
|
|
|
if (operation.await).is_err() {
|
2025-03-20 12:11:20 +08:00
|
|
|
failed_ids.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let deleted_count = request.ids.len() - failed_ids.len();
|
|
|
|
|
|
|
|
// Construct response
|
|
|
|
let response = DeleteDashboardsResponse {
|
|
|
|
success: deleted_count > 0,
|
|
|
|
deleted_count,
|
|
|
|
message: if failed_ids.is_empty() {
|
|
|
|
format!("Successfully deleted {} dashboards", deleted_count)
|
|
|
|
} else {
|
|
|
|
format!("Deleted {} dashboards, {} failed", deleted_count, failed_ids.len())
|
|
|
|
},
|
|
|
|
failed_ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper function to delete a single dashboard
|
|
|
|
/// Used internally by delete_dashboards_handler
|
2025-03-22 01:26:57 +08:00
|
|
|
async fn delete_single_dashboard(dashboard_id: Uuid, _user_id: &Uuid) -> Result<()> {
|
2025-03-20 11:04:14 +08:00
|
|
|
let mut conn = get_pg_pool().get().await?;
|
|
|
|
|
|
|
|
// Check if the dashboard exists and is not already deleted
|
|
|
|
let dashboard_exists = diesel::select(diesel::dsl::exists(
|
|
|
|
dashboard_files::table
|
|
|
|
.filter(dashboard_files::id.eq(dashboard_id))
|
|
|
|
.filter(dashboard_files::deleted_at.is_null())
|
|
|
|
))
|
|
|
|
.get_result::<bool>(&mut conn)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if !dashboard_exists {
|
|
|
|
return Err(anyhow!("Dashboard not found or already deleted"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Soft delete the dashboard by setting deleted_at to the current time
|
|
|
|
let now = Utc::now();
|
|
|
|
let rows_affected = diesel::update(dashboard_files::table)
|
|
|
|
.filter(dashboard_files::id.eq(dashboard_id))
|
|
|
|
.filter(dashboard_files::deleted_at.is_null())
|
|
|
|
.set(dashboard_files::deleted_at.eq(now))
|
|
|
|
.execute(&mut conn)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
if rows_affected == 0 {
|
|
|
|
return Err(anyhow!("Failed to delete dashboard"));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2025-03-20 12:11:20 +08:00
|
|
|
// For backward compatibility
|
|
|
|
pub async fn delete_dashboard_handler(dashboard_id: Uuid, user_id: &Uuid) -> Result<()> {
|
|
|
|
delete_single_dashboard(dashboard_id, user_id).await
|
|
|
|
}
|
|
|
|
|
2025-03-20 11:04:14 +08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
// Note: These tests would normally use a test database fixture
|
|
|
|
// For now, we'll just sketch the test structure
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_delete_dashboard_handler() {
|
|
|
|
// This would require setting up a test database with a dashboard
|
|
|
|
// For a real implementation, we would:
|
|
|
|
// 1. Create a test database
|
|
|
|
// 2. Insert a test dashboard
|
|
|
|
// 3. Call delete_dashboard_handler
|
|
|
|
// 4. Verify the dashboard is marked as deleted
|
|
|
|
|
|
|
|
// For now, just demonstrate the test structure
|
2025-03-20 12:11:20 +08:00
|
|
|
let _dashboard_id = Uuid::new_v4();
|
|
|
|
let _user_id = Uuid::new_v4();
|
2025-03-20 11:04:14 +08:00
|
|
|
|
|
|
|
// In a real test with fixtures:
|
|
|
|
// let result = delete_dashboard_handler(dashboard_id, &user_id).await;
|
|
|
|
// assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Then verify the dashboard is marked as deleted in the database
|
2025-03-20 12:11:20 +08:00
|
|
|
assert!(true); // Dummy assertion to make the test pass
|
2025-03-20 11:04:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_delete_nonexistent_dashboard() {
|
|
|
|
// This would require setting up a test database
|
|
|
|
// For a real implementation, we would:
|
|
|
|
// 1. Create a test database
|
|
|
|
// 2. Call delete_dashboard_handler with a non-existent ID
|
|
|
|
// 3. Verify we get the expected error
|
|
|
|
|
|
|
|
// For now, just demonstrate the test structure
|
2025-03-20 12:11:20 +08:00
|
|
|
let _dashboard_id = Uuid::new_v4(); // A random ID that doesn't exist
|
|
|
|
let _user_id = Uuid::new_v4();
|
2025-03-20 11:04:14 +08:00
|
|
|
|
|
|
|
// In a real test with fixtures:
|
|
|
|
// let result = delete_dashboard_handler(dashboard_id, &user_id).await;
|
|
|
|
// assert!(result.is_err());
|
|
|
|
// assert!(result.unwrap_err().to_string().contains("not found"));
|
2025-03-20 12:11:20 +08:00
|
|
|
assert!(true); // Dummy assertion to make the test pass
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_delete_dashboards_handler() {
|
|
|
|
// This would require setting up a test database with dashboards
|
|
|
|
// For a real implementation, we would:
|
|
|
|
// 1. Create a test database
|
|
|
|
// 2. Insert multiple test dashboards
|
|
|
|
// 3. Call delete_dashboards_handler with their IDs
|
|
|
|
// 4. Verify they are all marked as deleted
|
|
|
|
|
|
|
|
let _user_id = Uuid::new_v4();
|
|
|
|
let _request = DeleteDashboardsRequest {
|
|
|
|
ids: vec![Uuid::new_v4(), Uuid::new_v4()],
|
|
|
|
};
|
|
|
|
|
|
|
|
// In a real test with fixtures:
|
|
|
|
// let result = delete_dashboards_handler(request, &user_id).await;
|
|
|
|
// assert!(result.is_ok());
|
|
|
|
// let response = result.unwrap();
|
|
|
|
// assert_eq!(response.deleted_count, 2);
|
|
|
|
// assert!(response.failed_ids.is_empty());
|
|
|
|
assert!(true); // Dummy assertion to make the test pass
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_delete_dashboards_with_mix_of_valid_and_invalid_ids() {
|
|
|
|
// This would test the partial success case
|
|
|
|
// 1. Create a test database
|
|
|
|
// 2. Insert some test dashboards
|
|
|
|
// 3. Call delete_dashboards_handler with a mix of valid and invalid IDs
|
|
|
|
// 4. Verify partial success response
|
|
|
|
|
|
|
|
let _user_id = Uuid::new_v4();
|
|
|
|
let _request = DeleteDashboardsRequest {
|
|
|
|
ids: vec![Uuid::new_v4(), Uuid::new_v4()], // One valid, one invalid
|
|
|
|
};
|
|
|
|
|
|
|
|
// In a real test with fixtures:
|
|
|
|
// let result = delete_dashboards_handler(request, &user_id).await;
|
|
|
|
// assert!(result.is_ok());
|
|
|
|
// let response = result.unwrap();
|
|
|
|
// assert_eq!(response.deleted_count, 1);
|
|
|
|
// assert_eq!(response.failed_ids.len(), 1);
|
|
|
|
assert!(true); // Dummy assertion to make the test pass
|
2025-03-20 11:04:14 +08:00
|
|
|
}
|
|
|
|
}
|