buster/api/libs/handlers/src/collections/get_collection_handler.rs

225 lines
7.5 KiB
Rust
Raw Normal View History

2025-03-18 22:14:29 +08:00
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
2025-03-20 23:57:01 +08:00
use database::{
collections::fetch_collection,
enums::{AssetPermissionRole, AssetType, IdentityType},
pool::get_pg_pool,
schema::{
2025-03-21 06:36:26 +08:00
asset_permissions, collections_to_assets, dashboard_files, metric_files, users,
2025-03-20 23:57:01 +08:00
},
};
use diesel::{ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, Queryable};
use diesel_async::RunQueryDsl;
2025-03-20 23:46:49 +08:00
use tracing;
use uuid::Uuid;
2025-03-20 23:57:01 +08:00
use crate::collections::types::{
AssetUser, BusterShareIndividual, CollectionAsset, CollectionState, GetCollectionRequest,
};
#[derive(Queryable)]
struct AssetPermissionInfo {
role: AssetPermissionRole,
email: String,
name: Option<String>,
}
2025-03-20 23:46:49 +08:00
/// Type for querying asset data from database
#[derive(Queryable)]
struct AssetQueryResult {
id: Uuid,
name: String,
user_name: Option<String>,
2025-03-20 23:57:01 +08:00
email: Option<String>,
2025-03-20 23:46:49 +08:00
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
asset_type: AssetType,
}
/// Handler for getting a single collection by ID
///
/// # Arguments
/// * `user_id` - The ID of the user requesting the collection
/// * `req` - The request containing the collection ID
///
/// # Returns
/// * `Result<CollectionState>` - The collection state if found and accessible
2025-03-20 23:46:49 +08:00
/// Format database asset query results into CollectionAsset objects
fn format_assets(assets: Vec<AssetQueryResult>) -> Vec<CollectionAsset> {
assets
.into_iter()
2025-03-20 23:57:01 +08:00
.map(|asset| CollectionAsset {
id: asset.id,
name: asset.name,
created_by: AssetUser {
name: asset.user_name,
email: asset.email.unwrap_or("chad@buster.so".to_string()),
},
created_at: asset.created_at,
updated_at: asset.updated_at,
asset_type: asset.asset_type,
2025-03-20 23:46:49 +08:00
})
.collect()
}
pub async fn get_collection_handler(
_user_id: &Uuid,
req: GetCollectionRequest,
) -> Result<CollectionState> {
// Reuse the existing collection_utils function
2025-03-18 22:14:29 +08:00
let collection = match fetch_collection(&req.id).await? {
Some(collection) => collection,
None => return Err(anyhow!("Collection not found")),
};
2025-03-20 23:57:01 +08:00
let mut conn = match get_pg_pool().get().await {
Ok(conn) => conn,
Err(e) => return Err(anyhow!("Failed to get database connection: {}", e)),
};
2025-03-20 23:57:01 +08:00
// Query individual permissions for this collection
let individual_permissions_query = asset_permissions::table
.inner_join(users::table.on(users::id.eq(asset_permissions::identity_id)))
.filter(asset_permissions::asset_id.eq(req.id))
.filter(asset_permissions::asset_type.eq(AssetType::Collection))
.filter(asset_permissions::identity_type.eq(IdentityType::User))
.filter(asset_permissions::deleted_at.is_null())
.select((
asset_permissions::role,
users::email,
users::name,
))
.load::<AssetPermissionInfo>(&mut conn)
.await;
2025-03-20 23:57:01 +08:00
// For collections, we'll default public fields to false/none
// since the schema doesn't have these fields yet
2025-03-20 23:57:01 +08:00
let public_info: Result<(bool, Option<Uuid>, Option<DateTime<Utc>>), anyhow::Error> =
Ok((false, None, None));
// Convert AssetPermissionInfo to BusterShareIndividual
let individual_permissions = match individual_permissions_query {
Ok(permissions) => {
if permissions.is_empty() {
None
} else {
Some(
permissions
.into_iter()
.map(|p| BusterShareIndividual {
email: p.email,
role: p.role,
name: p.name,
})
.collect::<Vec<BusterShareIndividual>>(),
)
}
}
Err(_) => None,
};
2025-03-20 23:57:01 +08:00
// Get public access info
let (publicly_accessible, public_enabled_by, public_expiry_date) = match public_info {
Ok((accessible, enabled_by_id, expiry)) => {
// Get the user info for publicly_enabled_by if it exists
let enabled_by_email = if let Some(enabled_by_id) = enabled_by_id {
users::table
.filter(users::id.eq(enabled_by_id))
.select(users::email)
.first::<String>(&mut conn)
.await
.ok()
} else {
None
};
2025-03-20 23:57:01 +08:00
(accessible, enabled_by_email, expiry)
}
Err(_) => (false, None, None),
};
2025-03-18 22:14:29 +08:00
2025-03-20 23:46:49 +08:00
// Query for metric assets in the collection
let metric_assets_result = collections_to_assets::table
.inner_join(metric_files::table.on(metric_files::id.eq(collections_to_assets::asset_id)))
2025-03-20 23:57:01 +08:00
.left_join(users::table.on(users::id.eq(metric_files::created_by)))
2025-03-20 23:46:49 +08:00
.filter(collections_to_assets::collection_id.eq(req.id))
.filter(collections_to_assets::asset_type.eq(AssetType::MetricFile))
.filter(collections_to_assets::deleted_at.is_null())
.filter(metric_files::deleted_at.is_null())
.select((
metric_files::id,
metric_files::name,
2025-03-20 23:57:01 +08:00
users::name.nullable(),
users::email.nullable(),
2025-03-20 23:46:49 +08:00
metric_files::created_at,
metric_files::updated_at,
collections_to_assets::asset_type,
))
.load::<AssetQueryResult>(&mut conn)
.await;
// Query for dashboard assets in the collection
let dashboard_assets_result = collections_to_assets::table
2025-03-20 23:57:01 +08:00
.inner_join(
dashboard_files::table.on(dashboard_files::id.eq(collections_to_assets::asset_id)),
)
.left_join(users::table.on(users::id.eq(dashboard_files::created_by)))
2025-03-20 23:46:49 +08:00
.filter(collections_to_assets::collection_id.eq(req.id))
.filter(collections_to_assets::asset_type.eq(AssetType::DashboardFile))
.filter(collections_to_assets::deleted_at.is_null())
.filter(dashboard_files::deleted_at.is_null())
.select((
dashboard_files::id,
dashboard_files::name,
2025-03-20 23:57:01 +08:00
users::name.nullable(),
users::email.nullable(),
2025-03-20 23:46:49 +08:00
dashboard_files::created_at,
dashboard_files::updated_at,
collections_to_assets::asset_type,
))
.load::<AssetQueryResult>(&mut conn)
.await;
// Process metric assets
let metric_assets = match metric_assets_result {
Ok(assets) => assets,
Err(e) => {
tracing::error!("Failed to fetch metric assets: {}", e);
vec![]
}
};
// Process dashboard assets
let dashboard_assets = match dashboard_assets_result {
Ok(assets) => assets,
Err(e) => {
tracing::error!("Failed to fetch dashboard assets: {}", e);
vec![]
}
};
// Combine and format the assets
let mut combined_assets = metric_assets;
combined_assets.extend(dashboard_assets);
2025-03-20 23:57:01 +08:00
2025-03-20 23:46:49 +08:00
// Only include assets in the response if we found some
let formatted_assets = if combined_assets.is_empty() {
None
} else {
Some(format_assets(combined_assets))
};
2025-03-18 22:14:29 +08:00
Ok(CollectionState {
collection,
2025-03-20 23:46:49 +08:00
assets: formatted_assets,
2025-03-18 22:14:29 +08:00
permission: AssetPermissionRole::Owner,
organization_permissions: false,
individual_permissions,
publicly_accessible,
public_expiry_date,
public_enabled_by,
public_password: None,
2025-03-18 22:14:29 +08:00
})
}