diff --git a/api/libs/database/src/helpers/collections.rs b/api/libs/database/src/helpers/collections.rs new file mode 100644 index 000000000..b102c4f50 --- /dev/null +++ b/api/libs/database/src/helpers/collections.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use diesel::{ExpressionMethods, QueryDsl}; +use diesel_async::RunQueryDsl; +use uuid::Uuid; + +use crate::models::Collection; +use crate::pool::get_pg_pool; +use crate::schema::collections; + +/// Fetches a single collection by ID that hasn't been deleted +/// +/// # Arguments +/// * `id` - The UUID of the collection to fetch +/// +/// # Returns +/// * `Result>` - The collection if found and not deleted +pub async fn fetch_collection(id: &Uuid) -> Result> { + let mut conn = get_pg_pool().get().await?; + + let result = match collections::table + .filter(collections::id.eq(id)) + .filter(collections::deleted_at.is_null()) + .first::(&mut conn) + .await + { + Ok(result) => Some(result), + Err(diesel::NotFound) => None, + Err(e) => return Err(e.into()), + }; + + Ok(result) +} \ No newline at end of file diff --git a/api/libs/database/src/helpers/mod.rs b/api/libs/database/src/helpers/mod.rs index dfc4dd35c..93d552dc5 100644 --- a/api/libs/database/src/helpers/mod.rs +++ b/api/libs/database/src/helpers/mod.rs @@ -1,2 +1,3 @@ pub mod metric_files; pub mod dashboard_files; +pub mod collections; diff --git a/api/libs/handlers/src/collections/create_collection_handler.rs b/api/libs/handlers/src/collections/create_collection_handler.rs index c88b605e0..be2c8b859 100644 --- a/api/libs/handlers/src/collections/create_collection_handler.rs +++ b/api/libs/handlers/src/collections/create_collection_handler.rs @@ -38,7 +38,7 @@ pub async fn create_collection_handler( created_by: *user_id, updated_by: *user_id, deleted_at: None, - organization_id, + organization_id: *organization_id, }; let insert_task_user_id = *user_id; @@ -97,7 +97,7 @@ pub async fn create_collection_handler( // Update search index let collection_id_for_search = collection_id; let collection_name = collection.name.clone(); - let organization_id_for_search = organization_id; + let organization_id_for_search = *organization_id; let collection_search_handle = tokio::spawn(async move { let mut conn = match get_pg_pool().get().await { @@ -143,8 +143,6 @@ pub async fn create_collection_handler( collection, assets: None, permission: AssetPermissionRole::Owner, - individual_permissions: None, - team_permissions: None, organization_permissions: false, }) } diff --git a/api/libs/handlers/src/collections/delete_collection_handler.rs b/api/libs/handlers/src/collections/delete_collection_handler.rs index 4a20a0541..af2698de8 100644 --- a/api/libs/handlers/src/collections/delete_collection_handler.rs +++ b/api/libs/handlers/src/collections/delete_collection_handler.rs @@ -25,15 +25,6 @@ pub async fn delete_collection_handler( ids: Vec, ) -> Result { - // Filter out collections where the user only has viewer permission - let filtered_ids_to_delete: Vec = ids - .into_iter() - .filter(|id| match roles.get(id) { - Some(role) if *role != AssetPermissionRole::Viewer => true, - _ => false, - }) - .collect(); - // Get database connection let mut conn = match get_pg_pool().get().await { Ok(conn) => conn, @@ -44,7 +35,7 @@ pub async fn delete_collection_handler( // Soft delete the collections match update(collections::table) - .filter(collections::id.eq_any(&filtered_ids_to_delete)) + .filter(collections::id.eq_any(&ids)) .set(collections::deleted_at.eq(Some(Utc::now()))) .execute(&mut conn) .await @@ -57,6 +48,6 @@ pub async fn delete_collection_handler( // Return the IDs of the deleted collections Ok(DeleteCollectionResponse { - ids: filtered_ids_to_delete, + ids, }) } diff --git a/api/libs/handlers/src/collections/get_collection_handler.rs b/api/libs/handlers/src/collections/get_collection_handler.rs index 11ffc45a5..b58969527 100644 --- a/api/libs/handlers/src/collections/get_collection_handler.rs +++ b/api/libs/handlers/src/collections/get_collection_handler.rs @@ -1,4 +1,5 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; +use database::{collections::fetch_collection, enums::AssetPermissionRole}; use uuid::Uuid; use crate::collections::types::{CollectionState, GetCollectionRequest}; @@ -16,7 +17,15 @@ pub async fn get_collection_handler( req: GetCollectionRequest, ) -> Result { // Reuse the existing collection_utils function - let collection = database::utils::collections::get_collection_by_id(user_id, &req.id).await?; - - Ok(collection) + let collection = match fetch_collection(&req.id).await? { + Some(collection) => collection, + None => return Err(anyhow!("Collection not found")), + }; + + Ok(CollectionState { + collection, + assets: None, + permission: AssetPermissionRole::Owner, + organization_permissions: false, + }) } diff --git a/api/libs/handlers/src/collections/mod.rs b/api/libs/handlers/src/collections/mod.rs index 18e19b81f..41d5cf80c 100644 --- a/api/libs/handlers/src/collections/mod.rs +++ b/api/libs/handlers/src/collections/mod.rs @@ -1,17 +1,17 @@ // Collections handlers module -// mod create_collection_handler; -// mod delete_collection_handler; -// mod get_collection_handler; +mod create_collection_handler; +mod delete_collection_handler; +mod get_collection_handler; mod list_collections_handler; mod types; -// mod update_collection_handler; +mod update_collection_handler; // Re-export types pub use types::*; // Re-export handlers -// pub use create_collection_handler::create_collection_handler; -// pub use delete_collection_handler::delete_collection_handler; -// pub use get_collection_handler::get_collection_handler; +pub use create_collection_handler::create_collection_handler; +pub use delete_collection_handler::delete_collection_handler; +pub use get_collection_handler::get_collection_handler; pub use list_collections_handler::list_collections_handler; -// pub use update_collection_handler::update_collection_handler; +pub use update_collection_handler::update_collection_handler; diff --git a/api/libs/handlers/src/collections/types.rs b/api/libs/handlers/src/collections/types.rs index be4a50266..46f969cf0 100644 --- a/api/libs/handlers/src/collections/types.rs +++ b/api/libs/handlers/src/collections/types.rs @@ -96,17 +96,12 @@ pub struct UpdateCollectionAssetsRequest { pub type_: AssetType, } -// #[derive(Debug, Clone, Deserialize, Serialize)] -// pub struct UpdateCollectionRequest { -// pub id: Uuid, -// #[serde(flatten)] -// pub collection: Option, -// pub assets: Option>, -// pub team_permissions: Option>, -// pub user_permissions: Option>, -// pub remove_teams: Option>, -// pub remove_users: Option>, -// } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UpdateCollectionRequest { + pub id: Uuid, + pub collection: Option, + pub assets: Option>, +} // Delete collection types #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/api/libs/handlers/src/collections/update_collection_handler.rs b/api/libs/handlers/src/collections/update_collection_handler.rs index 59476e857..c55b7353b 100644 --- a/api/libs/handlers/src/collections/update_collection_handler.rs +++ b/api/libs/handlers/src/collections/update_collection_handler.rs @@ -1,12 +1,13 @@ use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetPermissionRole, AssetType}, + collections::fetch_collection, + enums::AssetPermissionRole, models::CollectionToAsset, pool::get_pg_pool, schema::{collections, collections_to_assets}, }; -use diesel::{dsl::not, update, AsChangeset, BoolExpressionMethods, ExpressionMethods}; +use diesel::{dsl::not, update, BoolExpressionMethods, ExpressionMethods}; use diesel_async::RunQueryDsl; use std::sync::Arc; use tokio; @@ -58,19 +59,6 @@ pub async fn update_collection_handler( }; // Wait for all update operations to complete - if let Some(update_collection_permissions_handle) = update_collection_permissions_handle { - match update_collection_permissions_handle.await { - Ok(Ok(_)) => (), - Ok(Err(e)) => { - tracing::error!("Error updating collection permissions: {}", e); - return Err(anyhow!("Error updating collection permissions: {}", e)); - } - Err(e) => { - tracing::error!("Error updating collection permissions: {}", e); - return Err(anyhow!("Error updating collection permissions: {}", e)); - } - } - } if let Some(update_collection_record_handle) = update_collection_record_handle { match update_collection_record_handle.await { @@ -101,14 +89,23 @@ pub async fn update_collection_handler( } // Get the updated collection - let collection = database::utils::collections::get_collection_by_id(user_id.as_ref(), &req.id).await?; - - Ok(collection) + let collection = match fetch_collection(&req.id).await? { + Some(collection) => collection, + None => return Err(anyhow!("Collection not found")), + }; + + Ok(CollectionState { + collection, + assets: None, + permission: AssetPermissionRole::Owner, + organization_permissions: false, + }) } /// Update collection record in the database /// /// # Arguments +/// # Arguments /// * `user_id` - The ID of the user updating the collection /// * `collection_id` - The ID of the collection to update /// * `collection` - The collection update object @@ -176,7 +173,7 @@ async fn update_collection_record( let query = diesel::sql_query( "UPDATE asset_search SET content = $1, updated_at = NOW() - WHERE asset_id = $2 AND asset_type = 'collection'" + WHERE asset_id = $2 AND asset_type = 'collection'", ) .bind::(collection_name) .bind::(*collection_id); @@ -248,7 +245,7 @@ async fn update_collection_assets( updated_by: *user_id, }) .collect(); - + match diesel::insert_into(collections_to_assets::table) .values(&new_asset_records) .on_conflict(( diff --git a/api/src/routes/rest/routes/collections/create_collection.rs b/api/src/routes/rest/routes/collections/create_collection.rs index 769995d8c..204c8ff6c 100644 --- a/api/src/routes/rest/routes/collections/create_collection.rs +++ b/api/src/routes/rest/routes/collections/create_collection.rs @@ -1,44 +1,30 @@ -use axum::{ - extract::State, - http::StatusCode, - Json, -}; -use handlers::collections::{create_collection_handler, CreateCollectionRequest, CollectionState}; +use axum::{extract::State, http::StatusCode, Extension, Json}; +use handlers::collections::{create_collection_handler, CollectionState, CreateCollectionRequest}; use middleware::AuthenticatedUser; use uuid::Uuid; -use database::utils::user::get_user_organization_id; /// Create a new collection /// /// This endpoint creates a new collection with the provided details. pub async fn create_collection( - user: AuthenticatedUser, + Extension(user): Extension, Json(req): Json, ) -> Result, (StatusCode, String)> { // Get the user's organization ID - let org_id = match get_user_organization_id(&user.id).await { - Ok(id) => id, - Err(e) => { - tracing::error!("Error getting user organization ID: {}", e); - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Error getting user organization: {}", e), - )); - } + let user_organization = match user.organizations.first() { + Some(org) => org, + None => return Err((StatusCode::NOT_FOUND, "User not found".to_string())), }; // Call the handler - match create_collection_handler(&user.id, &org_id, req).await { + match create_collection_handler(&user.id, &user_organization.id, req).await { Ok(collection) => Ok(Json(collection)), Err(e) => { tracing::error!("Error creating collection: {}", e); - + // Return appropriate error response based on the error if e.to_string().contains("permission") { - Err(( - StatusCode::FORBIDDEN, - format!("Permission denied: {}", e), - )) + Err((StatusCode::FORBIDDEN, format!("Permission denied: {}", e))) } else { Err(( StatusCode::INTERNAL_SERVER_ERROR, diff --git a/api/src/routes/rest/routes/collections/delete_collection.rs b/api/src/routes/rest/routes/collections/delete_collection.rs index 701ae0ac4..6d4ba8f51 100644 --- a/api/src/routes/rest/routes/collections/delete_collection.rs +++ b/api/src/routes/rest/routes/collections/delete_collection.rs @@ -1,11 +1,9 @@ -use axum::{ - http::StatusCode, - Json, +use axum::{http::StatusCode, Extension, Json}; +use handlers::collections::{ + delete_collection_handler, DeleteCollectionRequest, DeleteCollectionResponse, }; -use handlers::collections::{delete_collection_handler, DeleteCollectionRequest, DeleteCollectionResponse}; use middleware::AuthenticatedUser; use uuid::Uuid; -use database::utils::user::get_user_organization_id; /// Delete a collection /// @@ -14,12 +12,17 @@ pub async fn delete_collection( Extension(user): Extension, Json(req): Json, ) -> Result, (StatusCode, String)> { + let user_organization = match user.organizations.first() { + Some(org) => org, + None => return Err((StatusCode::NOT_FOUND, "User not found".to_string())), + }; + // Call the handler - match delete_collection_handler(&user.id, &user.organization_id, req.ids).await { + match delete_collection_handler(&user.id, &user_organization.id, req.ids).await { Ok(response) => Ok(Json(response)), Err(e) => { tracing::error!("Error deleting collection: {}", e); - + // Return appropriate error response based on the error if e.to_string().contains("not found") { Err(( @@ -27,10 +30,7 @@ pub async fn delete_collection( format!("Collection not found: {}", e), )) } else if e.to_string().contains("permission") { - Err(( - StatusCode::FORBIDDEN, - format!("Permission denied: {}", e), - )) + Err((StatusCode::FORBIDDEN, format!("Permission denied: {}", e))) } else { Err(( StatusCode::INTERNAL_SERVER_ERROR, diff --git a/api/src/routes/rest/routes/collections/get_collection.rs b/api/src/routes/rest/routes/collections/get_collection.rs index 8a47cfe54..8041d9779 100644 --- a/api/src/routes/rest/routes/collections/get_collection.rs +++ b/api/src/routes/rest/routes/collections/get_collection.rs @@ -1,12 +1,11 @@ use axum::{ - extract::{Path, State}, + extract::Path, http::StatusCode, - Json, + Extension, Json, }; -use handlers::collections::{get_collection_handler, CollectionState}; +use handlers::collections::{get_collection_handler, CollectionState, GetCollectionRequest}; use middleware::AuthenticatedUser; use uuid::Uuid; -use axum::extract::Extension; /// Get a collection by ID /// @@ -15,8 +14,10 @@ pub async fn get_collection( Extension(user): Extension, Path(id): Path, ) -> Result, (StatusCode, String)> { + let request = GetCollectionRequest { id }; + // Call the handler - match get_collection_handler(&user.id, &id).await { + match get_collection_handler(&user.id, request).await { Ok(collection) => Ok(Json(collection)), Err(e) => { tracing::error!("Error getting collection: {}", e); diff --git a/api/src/routes/rest/routes/collections/mod.rs b/api/src/routes/rest/routes/collections/mod.rs index 6fa212c8e..e49cca1b2 100644 --- a/api/src/routes/rest/routes/collections/mod.rs +++ b/api/src/routes/rest/routes/collections/mod.rs @@ -1,19 +1,19 @@ use axum::{ - routing::get, + routing::{get, post, put, delete}, Router, }; mod list_collections; -// mod get_collection; -// mod create_collection; -// mod update_collection; -// mod delete_collection; +mod get_collection; +mod create_collection; +mod update_collection; +mod delete_collection; pub fn router() -> Router { Router::new() .route("/", get(list_collections::list_collections)) - // .route("/", post(create_collection::create_collection)) - // .route("/:id", get(get_collection::get_collection)) - // .route("/:id", put(update_collection::update_collection)) - // .route("/:id", delete(delete_collection::delete_collection)) + .route("/", post(create_collection::create_collection)) + .route("/:id", get(get_collection::get_collection)) + .route("/:id", put(update_collection::update_collection)) + .route("/:id", delete(delete_collection::delete_collection)) } diff --git a/api/src/routes/rest/routes/collections/update_collection.rs b/api/src/routes/rest/routes/collections/update_collection.rs index bcb722283..169d92841 100644 --- a/api/src/routes/rest/routes/collections/update_collection.rs +++ b/api/src/routes/rest/routes/collections/update_collection.rs @@ -1,6 +1,6 @@ use axum::{ http::StatusCode, - Json, + Extension, Json, }; use handlers::collections::{update_collection_handler, UpdateCollectionRequest, CollectionState}; use middleware::AuthenticatedUser;