merging api_dashboards_update

This commit is contained in:
dal 2025-03-19 15:36:05 -06:00
commit 1c2808c5ad
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
9 changed files with 264 additions and 5 deletions

View File

@ -1,7 +1,9 @@
mod list_sharing_handler;
mod create_sharing_handler;
mod delete_sharing_handler;
mod update_sharing_handler;
pub use create_sharing_handler::create_dashboard_sharing_handler;
pub use delete_sharing_handler::delete_dashboard_sharing_handler;
pub use list_sharing_handler::list_dashboard_sharing_handler;
pub use update_sharing_handler::update_dashboard_sharing_handler;

View File

@ -0,0 +1,150 @@
use anyhow::{anyhow, Result};
use database::{
enums::{AssetPermissionRole, AssetType, IdentityType},
helpers::dashboard_files::fetch_dashboard_file,
};
use sharing::{
check_asset_permission::has_permission,
create_asset_permission::create_share_by_email,
};
use tracing::{error, info};
use uuid::Uuid;
/// Updates sharing permissions for a dashboard
///
/// # Arguments
///
/// * `dashboard_id` - The unique identifier of the dashboard
/// * `user_id` - The unique identifier of the user requesting the update
/// * `emails_and_roles` - A vector of (email, role) pairs to update
///
/// # Returns
///
/// Result indicating success or failure with error details
pub async fn update_dashboard_sharing_handler(
dashboard_id: &Uuid,
user_id: &Uuid,
emails_and_roles: Vec<(String, AssetPermissionRole)>,
) -> Result<()> {
info!(
dashboard_id = %dashboard_id,
user_id = %user_id,
recipients_count = emails_and_roles.len(),
"Updating dashboard sharing permissions"
);
// 1. Validate the dashboard exists
let _dashboard = match fetch_dashboard_file(dashboard_id).await? {
Some(dashboard) => dashboard,
None => {
error!(
dashboard_id = %dashboard_id,
"Dashboard not found during sharing update"
);
return Err(anyhow!("Dashboard not found"));
}
};
// 2. Check if user has permission to update sharing for the dashboard (Owner or FullAccess)
let has_permission_result = has_permission(
*dashboard_id,
AssetType::DashboardFile,
*user_id,
IdentityType::User,
AssetPermissionRole::FullAccess, // Owner role implicitly has FullAccess permissions
)
.await;
match has_permission_result {
Ok(true) => {
info!(
dashboard_id = %dashboard_id,
user_id = %user_id,
"User has permission to update dashboard sharing"
);
}
Ok(false) => {
error!(
dashboard_id = %dashboard_id,
user_id = %user_id,
"User does not have permission to update dashboard sharing"
);
return Err(anyhow!(
"User does not have permission to update sharing for this dashboard"
));
}
Err(e) => {
error!(
dashboard_id = %dashboard_id,
user_id = %user_id,
"Error checking permissions: {}", e
);
return Err(anyhow!("Error checking permissions: {}", e));
}
}
// 3. Process each email-role pair and update sharing permissions
for (email, role) in emails_and_roles {
// Validate email format
if !email.contains('@') {
error!("Invalid email format: {}", email);
return Err(anyhow!("Invalid email format: {}", email));
}
// Update (or create if not exists) the permission using create_share_by_email
// The create_share_by_email function handles both creation and updates with upsert
match create_share_by_email(&email, *dashboard_id, AssetType::DashboardFile, role, *user_id)
.await
{
Ok(_) => {
info!(
dashboard_id = %dashboard_id,
email = %email,
role = ?role,
"Updated sharing permission successfully"
);
}
Err(e) => {
error!(
dashboard_id = %dashboard_id,
email = %email,
"Failed to update sharing: {}", e
);
return Err(anyhow!("Failed to update sharing for email {}: {}", email, e));
}
}
}
info!(
dashboard_id = %dashboard_id,
user_id = %user_id,
updates_count = emails_and_roles.len(),
"Successfully updated all dashboard sharing permissions"
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_update_dashboard_sharing_invalid_email() {
// Test with invalid email format
let dashboard_id = Uuid::new_v4();
let user_id = Uuid::new_v4();
let emails_and_roles = vec![("invalid-email-format".to_string(), AssetPermissionRole::CanView)];
let result = update_dashboard_sharing_handler(&dashboard_id, &user_id, emails_and_roles).await;
assert!(result.is_err());
let error = result.unwrap_err().to_string();
assert!(error.contains("Invalid email format"));
}
// Note: For comprehensive tests, we would need to set up proper mocks
// for external dependencies like fetch_dashboard_file, has_permission,
// and create_share_by_email. These tests would be implemented in the
// integration tests directory.
}

View File

@ -15,7 +15,7 @@ The implementation is broken down into the following components, each with its o
2. **Create Dashboards Sharing Endpoint** - POST /dashboards/:id/sharing
- PRD: [api_dashboards_sharing_create.md](/Users/dallin/buster/buster/api/prds/active/api_dashboards_sharing_create.md)
3. **Update Dashboards Sharing Endpoint** - PUT /dashboards/:id/sharing
3. **Update Dashboards Sharing Endpoint** - PUT /dashboards/:id/sharing
- PRD: [api_dashboards_sharing_update.md](/Users/dallin/buster/buster/api/prds/active/api_dashboards_sharing_update.md)
4. **Delete Dashboards Sharing Endpoint** - DELETE /dashboards/:id/sharing ✅
@ -36,7 +36,7 @@ The PRDs can be developed in the following order, with opportunities for paralle
- These PRDs can be worked on simultaneously by different team members after the List PRD is complete.
- They use different sharing library functions and have minimal dependencies on each other.
3. **Third: Update Dashboards Sharing Endpoint PRD** (api_dashboards_sharing_update.md)
3. **Third: Update Dashboards Sharing Endpoint PRD** (api_dashboards_sharing_update.md)
- This PRD should be completed after the Create PRD since it builds on similar concepts and uses the same underlying sharing library functions.
- The update endpoint reuses many patterns from the create endpoint with slight modifications.
@ -66,7 +66,7 @@ After Phase 1 is complete, the following components can be implemented in parall
- Uses `create_share_by_email` from `@[api/libs/sharing/src]/create_asset_permission.rs`
- Uses `has_permission` from `@[api/libs/sharing/src]/check_asset_permission.rs`
- **Update Sharing Endpoint**
- **Update Sharing Endpoint**
- Uses `create_share_by_email` from `@[api/libs/sharing/src]/create_asset_permission.rs`
- Uses `has_permission` from `@[api/libs/sharing/src]/check_asset_permission.rs`

View File

@ -1,4 +1,4 @@
# API Dashboards Sharing - Update Endpoint PRD
# API Dashboards Sharing - Update Endpoint PRD
## Problem Statement
Users need the ability to update sharing permissions for dashboards through a REST API endpoint.

View File

@ -1,6 +1,6 @@
use axum::{
routing::delete,
routing::{get, post},
routing::{get, post, put},
Router,
};
@ -21,6 +21,10 @@ pub fn router() -> Router {
"/:id/sharing",
post(sharing::create_dashboard_sharing_rest_handler),
)
.route(
"/:id/sharing",
put(sharing::update_dashboard_sharing_rest_handler),
)
.route(
"/:id/sharing",
delete(sharing::delete_dashboard_sharing_rest_handler),

View File

@ -1,7 +1,9 @@
mod list_sharing;
mod create_sharing;
mod delete_sharing;
mod update_sharing;
pub use list_sharing::list_dashboard_sharing_rest_handler;
pub use create_sharing::create_dashboard_sharing_rest_handler;
pub use delete_sharing::delete_dashboard_sharing_rest_handler;
pub use update_sharing::update_dashboard_sharing_rest_handler;

View File

@ -0,0 +1,77 @@
use axum::{
extract::{Json, Path},
http::StatusCode,
Extension,
};
use database::enums::AssetPermissionRole;
use handlers::dashboards::sharing::update_dashboard_sharing_handler;
use middleware::AuthenticatedUser;
use serde::Deserialize;
use tracing::info;
use uuid::Uuid;
use crate::routes::rest::ApiResponse;
/// Structure for a single share recipient with their role
#[derive(Debug, Deserialize)]
pub struct ShareRecipient {
pub email: String,
pub role: AssetPermissionRole,
}
/// REST handler for updating sharing permissions for a dashboard
///
/// # Arguments
///
/// * `user` - The authenticated user making the request
/// * `id` - The unique identifier of the dashboard
/// * `request` - A list of ShareRecipient objects containing email and role
///
/// # Returns
///
/// A success message or appropriate error response
pub async fn update_dashboard_sharing_rest_handler(
Extension(user): Extension<AuthenticatedUser>,
Path(id): Path<Uuid>,
Json(request): Json<Vec<ShareRecipient>>,
) -> Result<ApiResponse<String>, (StatusCode, String)> {
info!(
dashboard_id = %id,
user_id = %user.id,
recipients_count = request.len(),
"Processing PUT request for dashboard sharing permissions"
);
let emails_and_roles: Vec<(String, AssetPermissionRole)> = request
.into_iter()
.map(|recipient| (recipient.email, recipient.role))
.collect();
match update_dashboard_sharing_handler(&id, &user.id, emails_and_roles).await {
Ok(_) => Ok(ApiResponse::JsonData(
"Sharing permissions updated successfully".to_string(),
)),
Err(e) => {
tracing::error!("Error updating sharing permissions: {}", e);
// Map specific errors to appropriate status codes
let error_message = e.to_string();
if error_message.contains("not found") {
return Err((StatusCode::NOT_FOUND, format!("Dashboard not found: {}", e)));
} else if error_message.contains("permission") {
return Err((
StatusCode::FORBIDDEN,
format!("Insufficient permissions: {}", e),
));
} else if error_message.contains("Invalid email") {
return Err((StatusCode::BAD_REQUEST, format!("Invalid email: {}", e)));
}
Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to update sharing permissions: {}", e),
))
}
}
}

View File

@ -1,3 +1,4 @@
mod list_sharing_test;
mod create_sharing_test;
mod delete_sharing_test;
mod update_sharing_test;

View File

@ -0,0 +1,23 @@
use database::enums::AssetPermissionRole;
use uuid::Uuid;
#[tokio::test]
async fn test_update_dashboard_sharing() {
// Test setup for actual integration tests would include:
// 1. Creating test users
// 2. Creating a test dashboard
// 3. Setting up initial permissions
// 4. Making a PUT request to update permissions
// 5. Verifying the permissions were updated correctly
// For now, we have a placeholder test
let _dashboard_id = Uuid::new_v4();
let _user_id = Uuid::new_v4();
let _emails_and_roles = vec![
("test1@example.com".to_string(), AssetPermissionRole::CanView),
("test2@example.com".to_string(), AssetPermissionRole::CanEdit),
];
// This is a placeholder assertion until we implement the full test
assert!(true);
}