From 7a63b97e3538f55a8fca9e786d4f04098f11b789 Mon Sep 17 00:00:00 2001 From: dal Date: Wed, 19 Mar 2025 09:35:56 -0600 Subject: [PATCH] Created sharing create permissions --- api/libs/sharing/Cargo.toml | 1 + .../sharing/src/create_asset_permission.rs | 96 ++++++++++++++++++- api/libs/sharing/src/errors.rs | 22 +++++ api/libs/sharing/src/lib.rs | 4 + api/libs/sharing/src/types.rs | 26 +++++ api/prds/active/sharing_create_permissions.md | 18 ++-- 6 files changed, 157 insertions(+), 10 deletions(-) diff --git a/api/libs/sharing/Cargo.toml b/api/libs/sharing/Cargo.toml index ff6f2caa3..8d448c324 100644 --- a/api/libs/sharing/Cargo.toml +++ b/api/libs/sharing/Cargo.toml @@ -13,6 +13,7 @@ tracing = { workspace = true } uuid = { workspace = true } diesel = { workspace = true } diesel-async = { workspace = true } +thiserror = { workspace = true } database = { path = "../database" } diff --git a/api/libs/sharing/src/create_asset_permission.rs b/api/libs/sharing/src/create_asset_permission.rs index 6db63ceef..64f4d06b7 100644 --- a/api/libs/sharing/src/create_asset_permission.rs +++ b/api/libs/sharing/src/create_asset_permission.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Result, anyhow}; use chrono::{DateTime, Utc}; use database::{ enums::{AssetPermissionRole, AssetType, IdentityType}, @@ -10,6 +10,9 @@ use diesel::{prelude::*, upsert::excluded}; use diesel_async::RunQueryDsl; use uuid::Uuid; +use crate::errors::SharingError; +use crate::types::find_user_by_email; + #[derive(Debug)] pub struct ShareCreationInput { pub asset_id: Uuid, @@ -70,6 +73,53 @@ pub async fn create_share( .context("Failed to create/update asset permission") } +/// Creates or updates an asset permission for a user identified by email +/// +/// # Arguments +/// * `email` - The email address of the user to grant access to +/// * `asset_id` - The ID of the asset to share +/// * `asset_type` - The type of asset (must not be deprecated) +/// * `role` - The permission role to assign (must be Owner or FullAccess) +/// * `created_by` - The ID of the user creating the permission +/// +/// # Returns +/// * `Result` - The created or updated permission record +pub async fn create_share_by_email( + email: &str, + asset_id: Uuid, + asset_type: AssetType, + role: AssetPermissionRole, + created_by: Uuid, +) -> Result { + // Validate asset type is not deprecated + if matches!(asset_type, AssetType::Dashboard | AssetType::Thread) { + return Err(anyhow!(SharingError::DeprecatedAssetType(format!("{:?}", asset_type)))); + } + + // Validate role is either Owner or FullAccess + if !matches!(role, AssetPermissionRole::Owner | AssetPermissionRole::FullAccess) { + return Err(anyhow!(SharingError::InvalidPermissionRole(format!( + "Role must be Owner or FullAccess, got {:?}", + role + )))); + } + + // Find the user by email + let user = find_user_by_email(email).await? + .ok_or_else(|| anyhow!(SharingError::UserNotFound(email.to_string())))?; + + // Create or update the permission + create_share( + asset_id, + asset_type, + user.id, + IdentityType::User, + role, + created_by, + ) + .await +} + /// Creates multiple sharing records in bulk pub async fn create_shares_bulk( shares: Vec, @@ -122,3 +172,47 @@ pub async fn create_shares_bulk( .await .context("Failed to create/update asset permissions in bulk") } + +#[cfg(test)] +mod tests { + use super::*; + use crate::errors::SharingError; + + #[tokio::test] + async fn test_create_share_by_email_validates_role() { + // Test that only Owner and FullAccess roles are accepted + let result = create_share_by_email( + "test@example.com", + Uuid::new_v4(), + AssetType::Chat, + AssetPermissionRole::CanEdit, + Uuid::new_v4(), + ) + .await; + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!(err.contains("Role must be Owner or FullAccess")); + } + + #[tokio::test] + async fn test_create_share_by_email_validates_asset_type() { + // Test that deprecated asset types are rejected + let result = create_share_by_email( + "test@example.com", + Uuid::new_v4(), + AssetType::Dashboard, // Deprecated asset type + AssetPermissionRole::Owner, + Uuid::new_v4(), + ) + .await; + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!(err.contains("Asset type")); + assert!(err.contains("is deprecated")); + } + + // Note: Additional integration tests would be needed to test the database interactions + // These would require mocking the database or using a test database +} diff --git a/api/libs/sharing/src/errors.rs b/api/libs/sharing/src/errors.rs index e69de29bb..24677b547 100644 --- a/api/libs/sharing/src/errors.rs +++ b/api/libs/sharing/src/errors.rs @@ -0,0 +1,22 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SharingError { + #[error("User not found: {0}")] + UserNotFound(String), + + #[error("Invalid permission role: {0}")] + InvalidPermissionRole(String), + + #[error("Asset type {0:?} is deprecated")] + DeprecatedAssetType(String), + + #[error("Permission denied: {0}")] + PermissionDenied(String), + + #[error("Database error: {0}")] + DatabaseError(#[from] diesel::result::Error), + + #[error("Unknown error: {0}")] + Unknown(String), +} \ No newline at end of file diff --git a/api/libs/sharing/src/lib.rs b/api/libs/sharing/src/lib.rs index 6c4804b35..b45945299 100644 --- a/api/libs/sharing/src/lib.rs +++ b/api/libs/sharing/src/lib.rs @@ -1,9 +1,13 @@ // pub mod check_asset_permission; pub mod create_asset_permission; +pub mod errors; pub mod list_asset_permissions; pub mod remove_asset_permissions; +pub mod types; // pub use check_asset_permission::check_access; pub use create_asset_permission::create_share; +pub use create_asset_permission::create_share_by_email; pub use list_asset_permissions::list_shares; pub use remove_asset_permissions::remove_share; +pub use types::find_user_by_email; diff --git a/api/libs/sharing/src/types.rs b/api/libs/sharing/src/types.rs index e69de29bb..5e9e6073b 100644 --- a/api/libs/sharing/src/types.rs +++ b/api/libs/sharing/src/types.rs @@ -0,0 +1,26 @@ +use anyhow::{Context, Result}; +use database::{ + models::User, + pool::get_pg_pool, + schema::users, +}; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; + +/// Finds a user by their email address +/// +/// # Arguments +/// * `email` - The email address to search for +/// +/// # Returns +/// * `Result>` - The user if found, None otherwise +pub async fn find_user_by_email(email: &str) -> Result> { + let mut conn = get_pg_pool().get().await?; + + users::table + .filter(users::email.eq(email)) + .first::(&mut conn) + .await + .optional() + .context("Failed to look up user by email") +} \ No newline at end of file diff --git a/api/prds/active/sharing_create_permissions.md b/api/prds/active/sharing_create_permissions.md index 8ad3f6611..df2161f1c 100644 --- a/api/prds/active/sharing_create_permissions.md +++ b/api/prds/active/sharing_create_permissions.md @@ -77,17 +77,17 @@ The function should handle the following error cases: - Error handling utilities ## Implementation Plan -1. Enhance the `create_asset_permission.rs` file -2. Implement the `create_share_by_email` function -3. Add validation and error handling -4. Write tests -5. Update the library exports in `lib.rs` +1. ✅ Enhance the `create_asset_permission.rs` file +2. ✅ Implement the `create_share_by_email` function +3. ✅ Add validation and error handling +4. ✅ Write tests +5. ✅ Update the library exports in `lib.rs` ## Success Criteria -- Function correctly creates or updates permissions using email addresses -- Appropriate validation and error handling is implemented -- Tests pass successfully -- Code is well-documented +- ✅ Function correctly creates or updates permissions using email addresses +- ✅ Appropriate validation and error handling is implemented +- ✅ Tests pass successfully +- ✅ Code is well-documented ## Permission Requirements - Requires Owner or FullAccess permission to execute