2025-03-18 22:14:29 +08:00
|
|
|
use anyhow::{anyhow, Result};
|
2025-03-20 10:37:18 +08:00
|
|
|
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::{
|
|
|
|
asset_permissions, collections, collections_to_assets, dashboard_files, metric_files, users,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
use diesel::{ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, Queryable};
|
2025-03-20 10:37:18 +08:00
|
|
|
use diesel_async::RunQueryDsl;
|
2025-03-20 23:46:49 +08:00
|
|
|
use tracing;
|
2025-03-12 02:16:28 +08:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2025-03-20 23:57:01 +08:00
|
|
|
use crate::collections::types::{
|
|
|
|
AssetUser, BusterShareIndividual, CollectionAsset, CollectionState, GetCollectionRequest,
|
|
|
|
};
|
2025-03-20 10:37:18 +08:00
|
|
|
|
|
|
|
#[derive(Queryable)]
|
|
|
|
struct AssetPermissionInfo {
|
|
|
|
identity_id: Uuid,
|
|
|
|
role: AssetPermissionRole,
|
|
|
|
email: String,
|
|
|
|
name: Option<String>,
|
|
|
|
}
|
2025-03-12 02:16:28 +08:00
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2025-03-12 02:16:28 +08:00
|
|
|
/// 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()
|
|
|
|
}
|
|
|
|
|
2025-03-12 02:16:28 +08:00
|
|
|
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
|
|
|
|
2025-03-20 10:37:18 +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
|
|
|
|
2025-03-20 10:37:18 +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::identity_id,
|
|
|
|
asset_permissions::role,
|
|
|
|
users::email,
|
|
|
|
users::name,
|
|
|
|
))
|
|
|
|
.load::<AssetPermissionInfo>(&mut conn)
|
|
|
|
.await;
|
2025-03-20 23:57:01 +08:00
|
|
|
|
2025-03-20 10:37:18 +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));
|
|
|
|
|
2025-03-20 10:37:18 +08:00
|
|
|
// 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
|
|
|
|
2025-03-20 10:37:18 +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
|
|
|
|
2025-03-20 10:37:18 +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,
|
2025-03-20 10:37:18 +08:00
|
|
|
individual_permissions,
|
|
|
|
publicly_accessible,
|
|
|
|
public_expiry_date,
|
|
|
|
public_enabled_by,
|
|
|
|
public_password: None,
|
2025-03-18 22:14:29 +08:00
|
|
|
})
|
2025-03-12 02:16:28 +08:00
|
|
|
}
|