2025-04-08 04:51:54 +08:00
|
|
|
---
|
|
|
|
title: Public Sharing Parameters Fix
|
|
|
|
author: Claude
|
|
|
|
date: 2024-04-07
|
|
|
|
status: Draft
|
|
|
|
parent_prd: project_bug_fixes_and_testing.md
|
|
|
|
ticket: BUS-1064
|
|
|
|
---
|
|
|
|
|
|
|
|
# Public Sharing Parameters Fix
|
|
|
|
|
|
|
|
## Problem Statement
|
|
|
|
|
|
|
|
Current behavior:
|
2025-04-08 05:37:33 +08:00
|
|
|
- Public sharing parameters using `Option<Option<T>>` are confusing and error-prone
|
|
|
|
- Diesel treats `None` as "don't update" vs `Some(None)` as "update to null"
|
|
|
|
- No clear distinction between "remove value" and "don't change value"
|
|
|
|
- Inconsistent handling between metrics and dashboards
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
Expected behavior:
|
2025-04-08 05:37:33 +08:00
|
|
|
- Clear enum for handling null vs none cases
|
|
|
|
- Consistent parameter handling across assets
|
|
|
|
- Proper diesel changeset updates
|
|
|
|
- Type-safe handling of public sharing fields
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
## Technical Design
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Parameter Types
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
```rust
|
2025-04-08 05:37:33 +08:00
|
|
|
/// Represents a field that can be either kept unchanged, set to null, or updated with a value
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
pub enum UpdateField<T> {
|
|
|
|
NoChange,
|
|
|
|
SetNull,
|
|
|
|
Update(T),
|
|
|
|
}
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
/// Request for updating sharing settings
|
2025-04-08 04:51:54 +08:00
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
2025-04-08 05:37:33 +08:00
|
|
|
pub struct UpdateSharingRequest {
|
|
|
|
/// List of users to share with
|
|
|
|
pub users: Option<Vec<ShareRecipient>>,
|
|
|
|
/// Whether the asset should be publicly accessible
|
|
|
|
pub publicly_accessible: Option<bool>,
|
|
|
|
/// Password for public access
|
|
|
|
pub public_password: UpdateField<String>,
|
|
|
|
/// Expiration date for public access
|
|
|
|
pub public_expiry_date: UpdateField<DateTime<Utc>>,
|
2025-04-08 04:51:54 +08:00
|
|
|
}
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
impl<T> UpdateField<T> {
|
|
|
|
/// Converts the UpdateField into an Option<Option<T>> for diesel
|
|
|
|
pub fn into_option(self) -> Option<Option<T>> {
|
|
|
|
match self {
|
|
|
|
UpdateField::NoChange => None,
|
|
|
|
UpdateField::SetNull => Some(None),
|
|
|
|
UpdateField::Update(value) => Some(Some(value)),
|
2025-04-08 04:51:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Handler Update
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
```rust
|
|
|
|
pub async fn update_sharing_handler(
|
|
|
|
asset_id: &Uuid,
|
|
|
|
user: &AuthenticatedUser,
|
2025-04-08 05:37:33 +08:00
|
|
|
request: UpdateSharingRequest,
|
|
|
|
) -> Result<()> {
|
|
|
|
// ... permission checks ...
|
|
|
|
|
|
|
|
let mut changeset = diesel::update(dsl::asset_files)
|
|
|
|
.filter(dsl::id.eq(asset_id));
|
|
|
|
|
|
|
|
// Only include fields in changeset if they should be updated
|
|
|
|
if let Some(publicly_accessible) = request.publicly_accessible {
|
|
|
|
changeset = changeset.set(dsl::publicly_accessible.eq(publicly_accessible));
|
|
|
|
|
|
|
|
// Update publicly_enabled_by based on publicly_accessible
|
|
|
|
let enabled_by = if publicly_accessible {
|
|
|
|
Some(user.id)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
changeset = changeset.set(dsl::publicly_enabled_by.eq(enabled_by));
|
2025-04-08 04:51:54 +08:00
|
|
|
}
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
// Handle public_password using UpdateField
|
|
|
|
match request.public_password {
|
|
|
|
UpdateField::Update(password) => {
|
|
|
|
changeset = changeset.set(dsl::public_password.eq(Some(password)));
|
|
|
|
}
|
|
|
|
UpdateField::SetNull => {
|
|
|
|
changeset = changeset.set(dsl::public_password.eq(None::<String>));
|
|
|
|
}
|
|
|
|
UpdateField::NoChange => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle public_expiry_date using UpdateField
|
|
|
|
match request.public_expiry_date {
|
|
|
|
UpdateField::Update(date) => {
|
|
|
|
changeset = changeset.set(dsl::public_expiry_date.eq(Some(date)));
|
|
|
|
}
|
|
|
|
UpdateField::SetNull => {
|
|
|
|
changeset = changeset.set(dsl::public_expiry_date.eq(None::<DateTime<Utc>>));
|
|
|
|
}
|
|
|
|
UpdateField::NoChange => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the update
|
|
|
|
changeset.execute(&mut conn).await?;
|
|
|
|
|
|
|
|
Ok(())
|
2025-04-08 04:51:54 +08:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Test Cases
|
|
|
|
|
|
|
|
```rust
|
2025-04-08 05:37:33 +08:00
|
|
|
// libs/handlers/tests/sharing/public_update_tests.rs
|
|
|
|
|
|
|
|
use chrono::{Duration, Utc};
|
|
|
|
use database::tests::common::{TestSetup, UserOrganizationRole};
|
|
|
|
use database::tests::common::assets::AssetTestHelpers;
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
#[tokio::test]
|
2025-04-08 05:37:33 +08:00
|
|
|
async fn test_metric_public_updates() -> Result<()> {
|
2025-04-08 04:51:54 +08:00
|
|
|
let setup = TestSetup::new(Some(UserOrganizationRole::Admin)).await?;
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
// Create test metric
|
|
|
|
let metric_id = AssetTestHelpers::create_test_metric(
|
|
|
|
&setup.db,
|
|
|
|
"Test Metric",
|
|
|
|
setup.organization.id
|
|
|
|
).await?;
|
|
|
|
|
|
|
|
// Test 1: Make public with password and expiry
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::Update("secretpass123".to_string()),
|
|
|
|
public_expiry_date: UpdateField::Update(Utc::now() + Duration::days(7)),
|
2025-04-08 04:51:54 +08:00
|
|
|
};
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify updates
|
|
|
|
let metric = fetch_metric_with_permission(&metric_id, &setup.user.id).await?;
|
|
|
|
assert!(metric.publicly_accessible);
|
|
|
|
assert_eq!(metric.publicly_enabled_by, Some(setup.user.id));
|
|
|
|
assert_eq!(metric.public_password, Some("secretpass123".to_string()));
|
|
|
|
assert!(metric.public_expiry_date.is_some());
|
|
|
|
|
|
|
|
// Test 2: Remove password but keep public
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: None, // Don't change
|
|
|
|
public_password: UpdateField::SetNull,
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
2025-04-08 04:51:54 +08:00
|
|
|
};
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify only password was removed
|
|
|
|
let metric = fetch_metric_with_permission(&metric_id, &setup.user.id).await?;
|
|
|
|
assert!(metric.publicly_accessible); // Still public
|
|
|
|
assert_eq!(metric.publicly_enabled_by, Some(setup.user.id));
|
|
|
|
assert_eq!(metric.public_password, None); // Password removed
|
|
|
|
assert!(metric.public_expiry_date.is_some()); // Expiry unchanged
|
|
|
|
|
|
|
|
// Test 3: Make private
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(false),
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify made private
|
|
|
|
let metric = fetch_metric_with_permission(&metric_id, &setup.user.id).await?;
|
|
|
|
assert!(!metric.publicly_accessible);
|
|
|
|
assert_eq!(metric.publicly_enabled_by, None);
|
|
|
|
assert_eq!(metric.public_password, None);
|
|
|
|
assert!(metric.public_expiry_date.is_some()); // Unchanged since NoChange
|
|
|
|
|
|
|
|
// Test 4: Clear all public settings
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(false),
|
|
|
|
public_password: UpdateField::SetNull,
|
|
|
|
public_expiry_date: UpdateField::SetNull,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify all public settings cleared
|
|
|
|
let metric = fetch_metric_with_permission(&metric_id, &setup.user.id).await?;
|
|
|
|
assert!(!metric.publicly_accessible);
|
|
|
|
assert_eq!(metric.publicly_enabled_by, None);
|
|
|
|
assert_eq!(metric.public_password, None);
|
|
|
|
assert_eq!(metric.public_expiry_date, None);
|
|
|
|
|
2025-04-08 04:51:54 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2025-04-08 05:37:33 +08:00
|
|
|
async fn test_dashboard_public_updates() -> Result<()> {
|
2025-04-08 04:51:54 +08:00
|
|
|
let setup = TestSetup::new(Some(UserOrganizationRole::Admin)).await?;
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
// Create test dashboard
|
|
|
|
let dashboard_id = AssetTestHelpers::create_test_dashboard(
|
|
|
|
&setup.db,
|
|
|
|
"Test Dashboard",
|
|
|
|
setup.organization.id
|
|
|
|
).await?;
|
|
|
|
|
|
|
|
// Test 1: Make public with expiry but no password
|
|
|
|
let request = UpdateDashboardSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::Update(Utc::now() + Duration::days(7)),
|
2025-04-08 04:51:54 +08:00
|
|
|
};
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
let result = update_dashboard_sharing_handler(&dashboard_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify updates
|
|
|
|
let dashboard = fetch_dashboard_with_permission(&dashboard_id, &setup.user.id).await?;
|
|
|
|
assert!(dashboard.publicly_accessible);
|
|
|
|
assert_eq!(dashboard.publicly_enabled_by, Some(setup.user.id));
|
|
|
|
assert_eq!(dashboard.public_password, None);
|
|
|
|
assert!(dashboard.public_expiry_date.is_some());
|
|
|
|
|
|
|
|
// Test 2: Add password to public dashboard
|
|
|
|
let request = UpdateDashboardSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: None, // Don't change
|
|
|
|
public_password: UpdateField::Update("dashpass123".to_string()),
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_dashboard_sharing_handler(&dashboard_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify password added
|
|
|
|
let dashboard = fetch_dashboard_with_permission(&dashboard_id, &setup.user.id).await?;
|
|
|
|
assert!(dashboard.publicly_accessible); // Still public
|
|
|
|
assert_eq!(dashboard.publicly_enabled_by, Some(setup.user.id));
|
|
|
|
assert_eq!(dashboard.public_password, Some("dashpass123".to_string()));
|
|
|
|
assert!(dashboard.public_expiry_date.is_some()); // Unchanged
|
|
|
|
|
|
|
|
// Test 3: Update expiry only
|
|
|
|
let new_expiry = Utc::now() + Duration::days(14);
|
|
|
|
let request = UpdateDashboardSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: None,
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::Update(new_expiry),
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_dashboard_sharing_handler(&dashboard_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify only expiry updated
|
|
|
|
let dashboard = fetch_dashboard_with_permission(&dashboard_id, &setup.user.id).await?;
|
|
|
|
assert!(dashboard.publicly_accessible); // Unchanged
|
|
|
|
assert_eq!(dashboard.publicly_enabled_by, Some(setup.user.id));
|
|
|
|
assert_eq!(dashboard.public_password, Some("dashpass123".to_string())); // Unchanged
|
|
|
|
assert!(dashboard.public_expiry_date.unwrap().timestamp() == new_expiry.timestamp());
|
|
|
|
|
|
|
|
// Test 4: Make private but keep password and expiry
|
|
|
|
let request = UpdateDashboardSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(false),
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_dashboard_sharing_handler(&dashboard_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify made private but kept other settings
|
|
|
|
let dashboard = fetch_dashboard_with_permission(&dashboard_id, &setup.user.id).await?;
|
|
|
|
assert!(!dashboard.publicly_accessible);
|
|
|
|
assert_eq!(dashboard.publicly_enabled_by, None);
|
|
|
|
assert_eq!(dashboard.public_password, Some("dashpass123".to_string())); // Unchanged
|
|
|
|
assert!(dashboard.public_expiry_date.is_some()); // Unchanged
|
|
|
|
|
|
|
|
// Test 5: Clear all public settings
|
|
|
|
let request = UpdateDashboardSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(false),
|
|
|
|
public_password: UpdateField::SetNull,
|
|
|
|
public_expiry_date: UpdateField::SetNull,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_dashboard_sharing_handler(&dashboard_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Verify all public settings cleared
|
|
|
|
let dashboard = fetch_dashboard_with_permission(&dashboard_id, &setup.user.id).await?;
|
|
|
|
assert!(!dashboard.publicly_accessible);
|
|
|
|
assert_eq!(dashboard.publicly_enabled_by, None);
|
|
|
|
assert_eq!(dashboard.public_password, None);
|
|
|
|
assert_eq!(dashboard.public_expiry_date, None);
|
|
|
|
|
2025-04-08 04:51:54 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2025-04-08 05:37:33 +08:00
|
|
|
async fn test_public_update_edge_cases() -> Result<()> {
|
2025-04-08 04:51:54 +08:00
|
|
|
let setup = TestSetup::new(Some(UserOrganizationRole::Admin)).await?;
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
// Create test metric
|
|
|
|
let metric_id = AssetTestHelpers::create_test_metric(
|
2025-04-08 04:51:54 +08:00
|
|
|
&setup.db,
|
2025-04-08 05:37:33 +08:00
|
|
|
"Test Metric",
|
2025-04-08 04:51:54 +08:00
|
|
|
setup.organization.id
|
|
|
|
).await?;
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
// Test 1: Make public with expired date (should fail)
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::Update(Utc::now() - Duration::days(1)),
|
2025-04-08 04:51:54 +08:00
|
|
|
};
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(result.unwrap_err().to_string().contains("expiry date must be in the future"));
|
|
|
|
|
|
|
|
// Test 2: Update with empty password (should fail)
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::Update("".to_string()),
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(result.unwrap_err().to_string().contains("password cannot be empty"));
|
|
|
|
|
|
|
|
// Test 3: Concurrent updates
|
|
|
|
let concurrent_setup = TestSetup::new(Some(UserOrganizationRole::Admin)).await?;
|
|
|
|
|
|
|
|
let request1 = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::Update("pass1".to_string()),
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let request2 = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::Update("pass2".to_string()),
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
let (result1, result2) = tokio::join!(
|
|
|
|
update_metric_sharing_handler(&metric_id, &setup.user, request1),
|
|
|
|
update_metric_sharing_handler(&metric_id, &concurrent_setup.user, request2)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(result1.is_ok() || result2.is_ok()); // At least one should succeed
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
// Verify final state (last write wins)
|
|
|
|
let metric = fetch_metric_with_permission(&metric_id, &setup.user.id).await?;
|
|
|
|
assert!(metric.publicly_accessible);
|
|
|
|
assert!(metric.public_password == Some("pass1".to_string()) ||
|
|
|
|
metric.public_password == Some("pass2".to_string()));
|
|
|
|
|
2025-04-08 04:51:54 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2025-04-08 05:37:33 +08:00
|
|
|
async fn test_public_update_permissions() -> Result<()> {
|
|
|
|
// Test with viewer role (should not be able to update public settings)
|
|
|
|
let setup = TestSetup::new(Some(UserOrganizationRole::Viewer)).await?;
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
let metric_id = AssetTestHelpers::create_test_metric(
|
2025-04-08 04:51:54 +08:00
|
|
|
&setup.db,
|
2025-04-08 05:37:33 +08:00
|
|
|
"Test Metric",
|
2025-04-08 04:51:54 +08:00
|
|
|
setup.organization.id
|
|
|
|
).await?;
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
let request = UpdateMetricSharingRequest {
|
|
|
|
users: None,
|
|
|
|
publicly_accessible: Some(true),
|
|
|
|
public_password: UpdateField::NoChange,
|
|
|
|
public_expiry_date: UpdateField::NoChange,
|
2025-04-08 04:51:54 +08:00
|
|
|
};
|
2025-04-08 05:37:33 +08:00
|
|
|
|
|
|
|
let result = update_metric_sharing_handler(&metric_id, &setup.user, request).await;
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(result.unwrap_err().to_string().contains("permission"));
|
|
|
|
|
2025-04-08 04:51:54 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
These tests cover:
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
1. Metric Public Updates:
|
|
|
|
- Making public with password and expiry
|
|
|
|
- Removing password while keeping public
|
|
|
|
- Making private
|
|
|
|
- Clearing all public settings
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
2. Dashboard Public Updates:
|
|
|
|
- Making public with expiry only
|
|
|
|
- Adding password to public dashboard
|
|
|
|
- Updating expiry only
|
|
|
|
- Making private while keeping settings
|
|
|
|
- Clearing all public settings
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
3. Edge Cases:
|
|
|
|
- Expired dates
|
|
|
|
- Empty passwords
|
|
|
|
- Concurrent updates
|
|
|
|
- Permission checks
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
Each test verifies:
|
|
|
|
- Correct field updates
|
|
|
|
- Field preservation when using `NoChange`
|
|
|
|
- Proper null handling with `SetNull`
|
|
|
|
- Proper enabled_by updates
|
|
|
|
- Permission enforcement
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
Would you like me to implement any additional test cases or modify the existing ones?
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
## Implementation Plan
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Phase 1: Type Updates
|
2025-04-08 06:18:37 +08:00
|
|
|
1. [x] Add `UpdateField` enum
|
|
|
|
2. [x] Update sharing request structs
|
|
|
|
3. [x] Add helper methods for diesel conversion
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Phase 2: Handler Updates
|
2025-04-08 06:18:37 +08:00
|
|
|
1. [x] Update metric sharing handler
|
|
|
|
2. [x] Update dashboard sharing handler
|
|
|
|
3. [x] Add changeset logic
|
|
|
|
4. [x] Update tests
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Phase 3: REST Updates
|
2025-04-08 06:18:37 +08:00
|
|
|
1. [x] Update REST handlers for new types
|
|
|
|
2. [x] Add request validation
|
|
|
|
3. [x] Update error handling
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
## Success Criteria
|
2025-04-08 06:18:37 +08:00
|
|
|
1. [x] Clear distinction between null and no-change cases
|
|
|
|
2. [x] Proper handling of public sharing fields
|
|
|
|
3. [x] Consistent behavior across assets
|
|
|
|
4. [x] All tests passing
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
## References
|
2025-04-08 04:51:54 +08:00
|
|
|
|
2025-04-08 05:37:33 +08:00
|
|
|
### Related Files
|
|
|
|
- `libs/handlers/src/metrics/sharing/update_sharing_handler.rs`
|
|
|
|
- `libs/handlers/src/dashboards/sharing/update_sharing_handler.rs`
|
|
|
|
- `server/src/routes/rest/routes/metrics/sharing/update_sharing.rs`
|
|
|
|
- `server/src/routes/rest/routes/dashboards/sharing/update_sharing.rs`
|
2025-04-08 04:51:54 +08:00
|
|
|
|
|
|
|
## Appendix
|
|
|
|
|
|
|
|
### Related Files
|
|
|
|
|
|
|
|
- `libs/handlers/src/sharing/validate.rs`
|
|
|
|
- `libs/handlers/src/sharing/update_sharing.rs`
|
|
|
|
- `libs/handlers/tests/sharing/sharing_params_test.rs`
|
|
|
|
- `libs/models/src/sharing.rs`
|
|
|
|
|
|
|
|
### Sharing Parameter Reference
|
|
|
|
|
|
|
|
Valid parameter combinations:
|
|
|
|
- Public sharing must allow anonymous access
|
|
|
|
- Public sharing limited to read-only access
|
|
|
|
- Expiration date must be in the future
|
|
|
|
- Non-public sharing can have any access level
|