mirror of https://github.com/buster-so/buster.git
Implement API metrics sharing creation functionality
This commit is contained in:
parent
f3c902e0c1
commit
6afc57ba5b
|
@ -0,0 +1,106 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use database::{
|
||||
enums::{AssetPermissionRole, AssetType, IdentityType},
|
||||
helpers::metric_files::fetch_metric_file,
|
||||
};
|
||||
use sharing::{
|
||||
check_asset_permission::has_permission,
|
||||
create_asset_permission::create_share_by_email,
|
||||
};
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Handler to create sharing permissions for a metric
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `metric_id` - The UUID of the metric to create sharing permissions for
|
||||
/// * `user_id` - The UUID of the user making the request
|
||||
/// * `emails` - List of emails to share the metric with
|
||||
/// * `role` - The role to assign to the shared users
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<()>` - Success if all sharing permissions were created
|
||||
pub async fn create_metric_sharing_handler(
|
||||
metric_id: &Uuid,
|
||||
user_id: &Uuid,
|
||||
emails: Vec<String>,
|
||||
role: AssetPermissionRole,
|
||||
) -> Result<()> {
|
||||
info!(
|
||||
metric_id = %metric_id,
|
||||
user_id = %user_id,
|
||||
emails_count = emails.len(),
|
||||
role = ?role,
|
||||
"Creating sharing permissions for metric"
|
||||
);
|
||||
|
||||
// 1. Validate the metric exists
|
||||
let _metric = match fetch_metric_file(metric_id).await? {
|
||||
Some(metric) => metric,
|
||||
None => return Err(anyhow!("Metric not found")),
|
||||
};
|
||||
|
||||
// 2. Check if user has permission to share the metric (Owner or FullAccess)
|
||||
let has_permission = has_permission(
|
||||
*metric_id,
|
||||
AssetType::MetricFile,
|
||||
*user_id,
|
||||
IdentityType::User,
|
||||
AssetPermissionRole::FullAccess, // Owner role implicitly has FullAccess permissions
|
||||
).await?;
|
||||
|
||||
if !has_permission {
|
||||
return Err(anyhow!("User does not have permission to share this metric"));
|
||||
}
|
||||
|
||||
// 3. Process each email and create sharing permissions
|
||||
for email in emails {
|
||||
// Validate email format
|
||||
if !email.contains('@') {
|
||||
return Err(anyhow!("Invalid email format: {}", email));
|
||||
}
|
||||
|
||||
// Create or update the permission using create_share_by_email
|
||||
match create_share_by_email(
|
||||
&email,
|
||||
*metric_id,
|
||||
AssetType::MetricFile,
|
||||
role,
|
||||
*user_id,
|
||||
).await {
|
||||
Ok(_) => {
|
||||
info!("Created sharing permission for email: {} on metric: {}", email, metric_id);
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(anyhow!("Failed to create sharing for email {}: {}", email, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_metric_sharing_invalid_email() {
|
||||
let metric_id = Uuid::new_v4();
|
||||
let user_id = Uuid::new_v4();
|
||||
let role = AssetPermissionRole::CanView;
|
||||
let emails = vec!["invalid-email-format".to_string()];
|
||||
|
||||
let result = create_metric_sharing_handler(&metric_id, &user_id, emails, role).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_metric_file, has_permission,
|
||||
// and create_share_by_email. This would typically involve using a
|
||||
// mocking framework that's compatible with async functions.
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
pub mod list_sharing_handler;
|
||||
pub mod create_sharing_handler;
|
||||
|
||||
pub use list_sharing_handler::*;
|
||||
pub use list_sharing_handler::*;
|
||||
pub use create_sharing_handler::*;
|
|
@ -1,4 +1,4 @@
|
|||
# API Metrics Sharing - Create Endpoint PRD
|
||||
# API Metrics Sharing - Create Endpoint PRD ✅
|
||||
|
||||
## Problem Statement
|
||||
Users need the ability to share metrics with other users via a REST API endpoint.
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
use axum::{
|
||||
extract::{Path, Json},
|
||||
http::StatusCode,
|
||||
Extension,
|
||||
};
|
||||
use database::enums::AssetPermissionRole;
|
||||
use handlers::metrics::sharing::create_metric_sharing_handler;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::routes::rest::ApiResponse;
|
||||
|
||||
/// Request structure for sharing a metric with users
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SharingRequest {
|
||||
pub emails: Vec<String>,
|
||||
pub role: AssetPermissionRole,
|
||||
}
|
||||
|
||||
/// REST handler for creating sharing permissions for a metric
|
||||
pub async fn create_metric_sharing_rest_handler(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(request): Json<SharingRequest>,
|
||||
) -> Result<ApiResponse<String>, (StatusCode, String)> {
|
||||
tracing::info!("Processing POST request for metric sharing with ID: {}, user_id: {}", id, user.id);
|
||||
|
||||
match create_metric_sharing_handler(&id, &user.id, request.emails, request.role).await {
|
||||
Ok(_) => Ok(ApiResponse::Success("Sharing permissions created successfully".to_string())),
|
||||
Err(e) => {
|
||||
tracing::error!("Error creating 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!("Metric 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 create sharing permissions: {}", e)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
use axum::{
|
||||
routing::get,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
mod list_sharing;
|
||||
mod create_sharing;
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(list_sharing::list_metric_sharing_rest_handler))
|
||||
.route("/", post(create_sharing::create_metric_sharing_rest_handler))
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
use axum::http::StatusCode;
|
||||
use chrono::Utc;
|
||||
use database::{
|
||||
enums::{AssetPermissionRole, AssetType, IdentityType},
|
||||
models::{AssetPermission},
|
||||
pool::get_pg_pool,
|
||||
schema::{users, metric_files, asset_permissions},
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::common::{
|
||||
http::client::TestClient,
|
||||
fixtures::{users::create_test_user, metrics::create_test_metric_file},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_metric_sharing_success() {
|
||||
// Setup test database connection
|
||||
let mut conn = get_pg_pool().get().await.unwrap();
|
||||
|
||||
// 1. Create test owner and shared user
|
||||
let owner = create_test_user("owner@example.com");
|
||||
let org_id = Uuid::new_v4(); // Need org id for fixture
|
||||
diesel::insert_into(users::table)
|
||||
.values(&owner)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let shared_user = create_test_user("shared@example.com");
|
||||
diesel::insert_into(users::table)
|
||||
.values(&shared_user)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 2. Create test metric
|
||||
let metric = create_test_metric_file(&owner.id, &org_id, None);
|
||||
diesel::insert_into(metric_files::table)
|
||||
.values(&metric)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 3. Create owner permission for test user
|
||||
let now = Utc::now();
|
||||
let permission = AssetPermission {
|
||||
identity_id: owner.id,
|
||||
identity_type: IdentityType::User,
|
||||
asset_id: metric.id,
|
||||
asset_type: AssetType::MetricFile,
|
||||
role: AssetPermissionRole::Owner,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
deleted_at: None,
|
||||
created_by: owner.id,
|
||||
updated_by: owner.id,
|
||||
};
|
||||
|
||||
diesel::insert_into(asset_permissions::table)
|
||||
.values(&permission)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 4. Create test client
|
||||
let client = TestClient::new().await;
|
||||
|
||||
// 5. Send create sharing request
|
||||
let response = client
|
||||
.post(&format!("/metrics/{}/sharing", metric.id))
|
||||
.with_auth(&owner.id.to_string())
|
||||
.json(&json!({
|
||||
"emails": ["shared@example.com"],
|
||||
"role": "CanView"
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// 6. Assert response
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// 7. Check that permission was created
|
||||
let permissions = asset_permissions::table
|
||||
.filter(asset_permissions::asset_id.eq(metric.id))
|
||||
.filter(asset_permissions::identity_id.eq(shared_user.id))
|
||||
.filter(asset_permissions::asset_type.eq(AssetType::MetricFile))
|
||||
.filter(asset_permissions::identity_type.eq(IdentityType::User))
|
||||
.filter(asset_permissions::deleted_at.is_null())
|
||||
.load::<AssetPermission>(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(permissions.len(), 1);
|
||||
assert_eq!(permissions[0].role, AssetPermissionRole::CanView);
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub mod list_sharing_test;
|
||||
pub mod list_sharing_test;
|
||||
pub mod create_sharing_test;
|
Loading…
Reference in New Issue