buster/api/libs/sharing/src/create_asset_permission.rs

219 lines
6.5 KiB
Rust
Raw Normal View History

2025-03-19 23:35:56 +08:00
use anyhow::{Context, Result, anyhow};
2025-03-12 05:09:19 +08:00
use chrono::{DateTime, Utc};
use database::{
enums::{AssetPermissionRole, AssetType, IdentityType},
models::AssetPermission,
pool::get_pg_pool,
schema::asset_permissions,
};
use diesel::{prelude::*, upsert::excluded};
use diesel_async::RunQueryDsl;
use uuid::Uuid;
2025-03-19 23:35:56 +08:00
use crate::errors::SharingError;
use crate::types::find_user_by_email;
2025-03-12 05:09:19 +08:00
#[derive(Debug)]
pub struct ShareCreationInput {
pub asset_id: Uuid,
pub asset_type: AssetType,
pub identity_id: Uuid,
pub identity_type: IdentityType,
pub role: AssetPermissionRole,
}
/// Creates a new sharing record for an asset
pub async fn create_share(
asset_id: Uuid,
asset_type: AssetType,
identity_id: Uuid,
identity_type: IdentityType,
role: AssetPermissionRole,
created_by: Uuid,
) -> Result<AssetPermission> {
let now = Utc::now();
// Validate asset type is not deprecated
if matches!(asset_type, AssetType::Dashboard | AssetType::Thread) {
anyhow::bail!("Asset type {:?} is deprecated", asset_type);
}
let mut conn = get_pg_pool().get().await?;
let permission = AssetPermission {
identity_id,
identity_type,
asset_id,
asset_type,
role,
created_at: now,
updated_at: now,
deleted_at: None,
created_by,
updated_by: created_by,
};
diesel::insert_into(asset_permissions::table)
.values(&permission)
.on_conflict((
asset_permissions::identity_id,
asset_permissions::asset_id,
asset_permissions::asset_type,
asset_permissions::identity_type,
))
.do_update()
.set((
asset_permissions::role.eq(role),
asset_permissions::updated_at.eq(now),
asset_permissions::updated_by.eq(created_by),
asset_permissions::deleted_at.eq::<Option<DateTime<Utc>>>(None),
))
.get_result(&mut conn)
.await
.context("Failed to create/update asset permission")
}
2025-03-19 23:35:56 +08:00
/// 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<AssetPermission>` - 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<AssetPermission> {
// 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
}
2025-03-12 05:09:19 +08:00
/// Creates multiple sharing records in bulk
pub async fn create_shares_bulk(
shares: Vec<ShareCreationInput>,
created_by: Uuid,
) -> Result<Vec<AssetPermission>> {
let now = Utc::now();
// Validate no deprecated asset types
if shares
.iter()
.any(|s| matches!(s.asset_type, AssetType::Dashboard | AssetType::Thread))
{
anyhow::bail!("Cannot create permissions for deprecated asset types");
}
let permissions: Vec<AssetPermission> = shares
.into_iter()
.map(|share| AssetPermission {
identity_id: share.identity_id,
identity_type: share.identity_type,
asset_id: share.asset_id,
asset_type: share.asset_type,
role: share.role,
created_at: now,
updated_at: now,
deleted_at: None,
created_by,
updated_by: created_by,
})
.collect();
let mut conn = get_pg_pool().get().await?;
diesel::insert_into(asset_permissions::table)
.values(&permissions)
.on_conflict((
asset_permissions::identity_id,
asset_permissions::asset_id,
asset_permissions::asset_type,
asset_permissions::identity_type,
))
.do_update()
.set((
asset_permissions::role.eq(excluded(asset_permissions::role)),
asset_permissions::updated_at.eq(now),
asset_permissions::updated_by.eq(created_by),
asset_permissions::deleted_at.eq::<Option<DateTime<Utc>>>(None),
))
.get_results(&mut conn)
.await
.context("Failed to create/update asset permissions in bulk")
}
2025-03-19 23:35:56 +08:00
#[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
}