6.3 KiB
API Dashboards Sharing - List Endpoint PRD
Implementation Status
✅ Implemented the REST handler for listing dashboard sharing permissions ✅ Implemented the business logic handler ✅ Added tests ✅ Connected to the existing sharing library
Problem Statement
Users need the ability to view all sharing permissions for a dashboard via a REST API endpoint.
Technical Design
Endpoint Specification
- Method: GET
- Path: /dashboards/:id/sharing
- Description: Lists all sharing permissions for a specific dashboard
- Authentication: Required
- Authorization: User must have at least ReadOnly access to the dashboard
Response Structure
#[derive(Debug, Serialize)]
pub struct SharingResponse {
pub permissions: Vec<SharingPermission>,
}
#[derive(Debug, Serialize)]
pub struct SharingPermission {
pub user_id: Uuid,
pub email: String,
pub name: Option<String>,
pub avatar_url: Option<String>,
pub role: AssetPermissionRole,
}
Implementation Details
New Files
/src/routes/rest/routes/dashboards/sharing/list_sharing.rs
- REST handler for listing sharing permissions/libs/handlers/src/dashboards/sharing/list_sharing_handler.rs
- Business logic for listing sharing permissions
REST Handler Implementation
// list_sharing.rs
pub async fn list_dashboard_sharing_rest_handler(
Extension(user): Extension<AuthenticatedUser>,
Path(id): Path<Uuid>,
) -> Result<ApiResponse<SharingResponse>, (StatusCode, String)> {
tracing::info!("Processing GET request for dashboard sharing with ID: {}, user_id: {}", id, user.id);
match list_dashboard_sharing_handler(&id, &user.id).await {
Ok(permissions) => {
let response = SharingResponse {
permissions: permissions.into_iter().map(|p| SharingPermission {
user_id: p.user.as_ref().map(|u| u.id).unwrap_or_default(),
email: p.user.as_ref().map(|u| u.email.clone()).unwrap_or_default(),
name: p.user.as_ref().and_then(|u| u.name.clone()),
avatar_url: p.user.as_ref().and_then(|u| u.avatar_url.clone()),
role: p.permission.role,
}).collect(),
};
Ok(ApiResponse::Success(response))
},
Err(e) => {
tracing::error!("Error listing sharing permissions: {}", e);
Err((StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to list sharing permissions: {}", e)))
}
}
}
Handler Implementation
// list_sharing_handler.rs
pub async fn list_dashboard_sharing_handler(
dashboard_id: &Uuid,
user_id: &Uuid,
) -> Result<Vec<AssetPermissionWithUser>> {
// 1. Validate the dashboard exists
let dashboard = match get_dashboard_by_id(dashboard_id).await {
Ok(Some(dashboard)) => dashboard,
Ok(None) => return Err(anyhow!("Dashboard not found")),
Err(e) => return Err(anyhow!("Error fetching dashboard: {}", e)),
};
// 2. Check if user has permission to view the dashboard
let user_role = check_access(
*dashboard_id,
AssetType::Dashboard,
*user_id,
IdentityType::User,
).await?;
if user_role.is_none() {
return Err(anyhow!("User does not have permission to view this dashboard"));
}
// 3. Get all permissions for the dashboard
let permissions = list_shares(
*dashboard_id,
AssetType::Dashboard,
).await?;
Ok(permissions)
}
Sharing Library Integration
This endpoint leverages the following functions from the sharing library:
check_access
from@[api/libs/sharing/src]/check_asset_permission.rs
:
pub async fn check_access(
asset_id: Uuid,
asset_type: AssetType,
identity_id: Uuid,
identity_type: IdentityType,
) -> Result<Option<AssetPermissionRole>>
This function is used to verify that the user has permission to view the dashboard. It returns the user's role for the asset, or None if they don't have access.
list_shares
from@[api/libs/sharing/src]/list_asset_permissions.rs
:
pub async fn list_shares(
asset_id: Uuid,
asset_type: AssetType,
) -> Result<Vec<AssetPermissionWithUser>>
This function retrieves all permissions for a specified asset, including user information. It filters out soft-deleted permissions and returns a structured response.
- The
AssetPermissionWithUser
type from@[api/libs/sharing/src]/types.rs
:
pub struct AssetPermissionWithUser {
pub permission: SerializableAssetPermission,
pub user: Option<UserInfo>,
}
This type combines permission data with user information for a comprehensive response.
Error Handling
The handler will return appropriate error responses:
- 404 Not Found - If the dashboard doesn't exist
- 403 Forbidden - If the user doesn't have permission to view the dashboard
- 500 Internal Server Error - For database errors or other unexpected issues
Testing Strategy
Unit Tests
- Test permission validation logic
- Test error handling for non-existent dashboards
- Test error handling for unauthorized users
- Test mapping from
AssetPermissionWithUser
toSharingPermission
Integration Tests
- Test GET /dashboards/:id/sharing with valid ID and authorized user
- Test GET /dashboards/:id/sharing with valid ID and unauthorized user
- Test GET /dashboards/:id/sharing with non-existent dashboard ID
- Test GET /dashboards/:id/sharing with dashboard that has no sharing permissions
Test Cases
- Should return all sharing permissions for a dashboard when user has access
- Should return 403 when user doesn't have access to the dashboard
- Should return 404 when dashboard doesn't exist
- Should return empty array when no sharing permissions exist
Performance Considerations
- The
list_shares
function performs a database join between asset_permissions and users tables - For dashboards with many sharing permissions, consider pagination in a future enhancement
Security Considerations
- Ensure that only users with at least ReadOnly access can view sharing permissions
- Validate the dashboard ID to prevent injection attacks
- Do not expose sensitive user information beyond what's needed
Monitoring
- Log all requests with appropriate context
- Track performance metrics for the endpoint
- Monitor error rates and types