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:41:29 +08:00
|
|
|
use crate::{errors::SharingError, user_lookup::find_user_by_email};
|
2025-03-19 23:35:56 +08:00
|
|
|
|
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) {
|
2025-03-19 23:41:29 +08:00
|
|
|
return Err(SharingError::DeprecatedAssetType(format!("{:?}", asset_type)).into());
|
2025-03-12 05:09:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2025-03-19 23:48:56 +08:00
|
|
|
/// * `role` - The permission role to assign
|
2025-03-19 23:35:56 +08:00
|
|
|
/// * `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) {
|
2025-03-19 23:48:56 +08:00
|
|
|
return Err(SharingError::DeprecatedAssetType(format!("{:?}", asset_type)).into());
|
2025-03-19 23:35:56 +08:00
|
|
|
}
|
|
|
|
|
2025-03-19 23:41:29 +08:00
|
|
|
// Validate email format
|
|
|
|
if !email.contains('@') {
|
|
|
|
return Err(SharingError::InvalidEmail(email.to_string()).into());
|
2025-03-19 23:35:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find the user by email
|
2025-03-19 23:41:29 +08:00
|
|
|
let user = find_user_by_email(email)
|
|
|
|
.await?
|
|
|
|
.ok_or_else(|| SharingError::UserNotFound(email.to_string()))?;
|
2025-03-19 23:35:56 +08:00
|
|
|
|
2025-03-19 23:41:29 +08:00
|
|
|
// Create the share for the user
|
2025-03-19 23:35:56 +08:00
|
|
|
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))
|
|
|
|
{
|
2025-03-19 23:41:29 +08:00
|
|
|
return Err(SharingError::DeprecatedAssetType("Cannot create permissions for deprecated asset types".to_string()).into());
|
2025-03-12 05:09:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2025-03-19 23:41:29 +08:00
|
|
|
use database::enums::{AssetPermissionRole, AssetType, IdentityType};
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid_email_format() {
|
|
|
|
let runtime = tokio::runtime::Runtime::new().unwrap();
|
|
|
|
let result = runtime.block_on(create_share_by_email(
|
|
|
|
"not-an-email",
|
2025-03-19 23:35:56 +08:00
|
|
|
Uuid::new_v4(),
|
2025-03-19 23:41:29 +08:00
|
|
|
AssetType::Collection,
|
|
|
|
AssetPermissionRole::Viewer,
|
2025-03-19 23:35:56 +08:00
|
|
|
Uuid::new_v4(),
|
2025-03-19 23:41:29 +08:00
|
|
|
));
|
|
|
|
|
2025-03-19 23:35:56 +08:00
|
|
|
assert!(result.is_err());
|
|
|
|
let err = result.unwrap_err().to_string();
|
2025-03-19 23:41:29 +08:00
|
|
|
assert!(err.contains("Invalid email"));
|
2025-03-19 23:35:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
2025-03-20 00:17:05 +08:00
|
|
|
assert!(err.contains("Deprecated asset type"));
|
|
|
|
assert!(err.contains("Dashboard"));
|
2025-03-19 23:35:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Note: Additional integration tests would be needed to test the database interactions
|
|
|
|
// These would require mocking the database or using a test database
|
|
|
|
}
|