start of collections endpoints

This commit is contained in:
dal 2025-03-18 08:14:29 -06:00
parent e790547c2f
commit bd2cbf781c
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
13 changed files with 117 additions and 107 deletions

View File

@ -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<Option<Collection>>` - The collection if found and not deleted
pub async fn fetch_collection(id: &Uuid) -> Result<Option<Collection>> {
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::<Collection>(&mut conn)
.await
{
Ok(result) => Some(result),
Err(diesel::NotFound) => None,
Err(e) => return Err(e.into()),
};
Ok(result)
}

View File

@ -1,2 +1,3 @@
pub mod metric_files; pub mod metric_files;
pub mod dashboard_files; pub mod dashboard_files;
pub mod collections;

View File

@ -38,7 +38,7 @@ pub async fn create_collection_handler(
created_by: *user_id, created_by: *user_id,
updated_by: *user_id, updated_by: *user_id,
deleted_at: None, deleted_at: None,
organization_id, organization_id: *organization_id,
}; };
let insert_task_user_id = *user_id; let insert_task_user_id = *user_id;
@ -97,7 +97,7 @@ pub async fn create_collection_handler(
// Update search index // Update search index
let collection_id_for_search = collection_id; let collection_id_for_search = collection_id;
let collection_name = collection.name.clone(); 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 collection_search_handle = tokio::spawn(async move {
let mut conn = match get_pg_pool().get().await { let mut conn = match get_pg_pool().get().await {
@ -143,8 +143,6 @@ pub async fn create_collection_handler(
collection, collection,
assets: None, assets: None,
permission: AssetPermissionRole::Owner, permission: AssetPermissionRole::Owner,
individual_permissions: None,
team_permissions: None,
organization_permissions: false, organization_permissions: false,
}) })
} }

View File

@ -25,15 +25,6 @@ pub async fn delete_collection_handler(
ids: Vec<Uuid>, ids: Vec<Uuid>,
) -> Result<DeleteCollectionResponse> { ) -> Result<DeleteCollectionResponse> {
// Filter out collections where the user only has viewer permission
let filtered_ids_to_delete: Vec<Uuid> = ids
.into_iter()
.filter(|id| match roles.get(id) {
Some(role) if *role != AssetPermissionRole::Viewer => true,
_ => false,
})
.collect();
// Get database connection // Get database connection
let mut conn = match get_pg_pool().get().await { let mut conn = match get_pg_pool().get().await {
Ok(conn) => conn, Ok(conn) => conn,
@ -44,7 +35,7 @@ pub async fn delete_collection_handler(
// Soft delete the collections // Soft delete the collections
match update(collections::table) 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()))) .set(collections::deleted_at.eq(Some(Utc::now())))
.execute(&mut conn) .execute(&mut conn)
.await .await
@ -57,6 +48,6 @@ pub async fn delete_collection_handler(
// Return the IDs of the deleted collections // Return the IDs of the deleted collections
Ok(DeleteCollectionResponse { Ok(DeleteCollectionResponse {
ids: filtered_ids_to_delete, ids,
}) })
} }

View File

@ -1,4 +1,5 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use database::{collections::fetch_collection, enums::AssetPermissionRole};
use uuid::Uuid; use uuid::Uuid;
use crate::collections::types::{CollectionState, GetCollectionRequest}; use crate::collections::types::{CollectionState, GetCollectionRequest};
@ -16,7 +17,15 @@ pub async fn get_collection_handler(
req: GetCollectionRequest, req: GetCollectionRequest,
) -> Result<CollectionState> { ) -> Result<CollectionState> {
// Reuse the existing collection_utils function // Reuse the existing collection_utils function
let collection = database::utils::collections::get_collection_by_id(user_id, &req.id).await?; let collection = match fetch_collection(&req.id).await? {
Some(collection) => collection,
None => return Err(anyhow!("Collection not found")),
};
Ok(collection) Ok(CollectionState {
collection,
assets: None,
permission: AssetPermissionRole::Owner,
organization_permissions: false,
})
} }

View File

@ -1,17 +1,17 @@
// Collections handlers module // Collections handlers module
// mod create_collection_handler; mod create_collection_handler;
// mod delete_collection_handler; mod delete_collection_handler;
// mod get_collection_handler; mod get_collection_handler;
mod list_collections_handler; mod list_collections_handler;
mod types; mod types;
// mod update_collection_handler; mod update_collection_handler;
// Re-export types // Re-export types
pub use types::*; pub use types::*;
// Re-export handlers // Re-export handlers
// pub use create_collection_handler::create_collection_handler; pub use create_collection_handler::create_collection_handler;
// pub use delete_collection_handler::delete_collection_handler; pub use delete_collection_handler::delete_collection_handler;
// pub use get_collection_handler::get_collection_handler; pub use get_collection_handler::get_collection_handler;
pub use list_collections_handler::list_collections_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;

View File

@ -96,17 +96,12 @@ pub struct UpdateCollectionAssetsRequest {
pub type_: AssetType, pub type_: AssetType,
} }
// #[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
// pub struct UpdateCollectionRequest { pub struct UpdateCollectionRequest {
// pub id: Uuid, pub id: Uuid,
// #[serde(flatten)] pub collection: Option<UpdateCollectionObject>,
// pub collection: Option<UpdateCollectionObject>, pub assets: Option<Vec<UpdateCollectionAssetsRequest>>,
// pub assets: Option<Vec<UpdateCollectionAssetsRequest>>, }
// pub team_permissions: Option<Vec<database::utils::sharing::asset_sharing::ShareWithTeamsReqObject>>,
// pub user_permissions: Option<Vec<database::utils::sharing::asset_sharing::ShareWithUsersReqObject>>,
// pub remove_teams: Option<Vec<Uuid>>,
// pub remove_users: Option<Vec<Uuid>>,
// }
// Delete collection types // Delete collection types
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]

View File

@ -1,12 +1,13 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use database::{ use database::{
enums::{AssetPermissionRole, AssetType}, collections::fetch_collection,
enums::AssetPermissionRole,
models::CollectionToAsset, models::CollectionToAsset,
pool::get_pg_pool, pool::get_pg_pool,
schema::{collections, collections_to_assets}, 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 diesel_async::RunQueryDsl;
use std::sync::Arc; use std::sync::Arc;
use tokio; use tokio;
@ -58,19 +59,6 @@ pub async fn update_collection_handler(
}; };
// Wait for all update operations to complete // 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 { if let Some(update_collection_record_handle) = update_collection_record_handle {
match update_collection_record_handle.await { match update_collection_record_handle.await {
@ -101,14 +89,23 @@ pub async fn update_collection_handler(
} }
// Get the updated collection // Get the updated collection
let collection = database::utils::collections::get_collection_by_id(user_id.as_ref(), &req.id).await?; let collection = match fetch_collection(&req.id).await? {
Some(collection) => collection,
None => return Err(anyhow!("Collection not found")),
};
Ok(collection) Ok(CollectionState {
collection,
assets: None,
permission: AssetPermissionRole::Owner,
organization_permissions: false,
})
} }
/// Update collection record in the database /// Update collection record in the database
/// ///
/// # Arguments /// # Arguments
/// # Arguments
/// * `user_id` - The ID of the user updating the collection /// * `user_id` - The ID of the user updating the collection
/// * `collection_id` - The ID of the collection to update /// * `collection_id` - The ID of the collection to update
/// * `collection` - The collection update object /// * `collection` - The collection update object
@ -176,7 +173,7 @@ async fn update_collection_record(
let query = diesel::sql_query( let query = diesel::sql_query(
"UPDATE asset_search "UPDATE asset_search
SET content = $1, updated_at = NOW() SET content = $1, updated_at = NOW()
WHERE asset_id = $2 AND asset_type = 'collection'" WHERE asset_id = $2 AND asset_type = 'collection'",
) )
.bind::<diesel::sql_types::Text, _>(collection_name) .bind::<diesel::sql_types::Text, _>(collection_name)
.bind::<diesel::sql_types::Uuid, _>(*collection_id); .bind::<diesel::sql_types::Uuid, _>(*collection_id);

View File

@ -1,44 +1,30 @@
use axum::{ use axum::{extract::State, http::StatusCode, Extension, Json};
extract::State, use handlers::collections::{create_collection_handler, CollectionState, CreateCollectionRequest};
http::StatusCode,
Json,
};
use handlers::collections::{create_collection_handler, CreateCollectionRequest, CollectionState};
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;
use uuid::Uuid; use uuid::Uuid;
use database::utils::user::get_user_organization_id;
/// Create a new collection /// Create a new collection
/// ///
/// This endpoint creates a new collection with the provided details. /// This endpoint creates a new collection with the provided details.
pub async fn create_collection( pub async fn create_collection(
user: AuthenticatedUser, Extension(user): Extension<AuthenticatedUser>,
Json(req): Json<CreateCollectionRequest>, Json(req): Json<CreateCollectionRequest>,
) -> Result<Json<CollectionState>, (StatusCode, String)> { ) -> Result<Json<CollectionState>, (StatusCode, String)> {
// Get the user's organization ID // Get the user's organization ID
let org_id = match get_user_organization_id(&user.id).await { let user_organization = match user.organizations.first() {
Ok(id) => id, Some(org) => org,
Err(e) => { None => return Err((StatusCode::NOT_FOUND, "User not found".to_string())),
tracing::error!("Error getting user organization ID: {}", e);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Error getting user organization: {}", e),
));
}
}; };
// Call the handler // 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)), Ok(collection) => Ok(Json(collection)),
Err(e) => { Err(e) => {
tracing::error!("Error creating collection: {}", e); tracing::error!("Error creating collection: {}", e);
// Return appropriate error response based on the error // Return appropriate error response based on the error
if e.to_string().contains("permission") { if e.to_string().contains("permission") {
Err(( Err((StatusCode::FORBIDDEN, format!("Permission denied: {}", e)))
StatusCode::FORBIDDEN,
format!("Permission denied: {}", e),
))
} else { } else {
Err(( Err((
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,

View File

@ -1,11 +1,9 @@
use axum::{ use axum::{http::StatusCode, Extension, Json};
http::StatusCode, use handlers::collections::{
Json, delete_collection_handler, DeleteCollectionRequest, DeleteCollectionResponse,
}; };
use handlers::collections::{delete_collection_handler, DeleteCollectionRequest, DeleteCollectionResponse};
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;
use uuid::Uuid; use uuid::Uuid;
use database::utils::user::get_user_organization_id;
/// Delete a collection /// Delete a collection
/// ///
@ -14,8 +12,13 @@ pub async fn delete_collection(
Extension(user): Extension<AuthenticatedUser>, Extension(user): Extension<AuthenticatedUser>,
Json(req): Json<DeleteCollectionRequest>, Json(req): Json<DeleteCollectionRequest>,
) -> Result<Json<DeleteCollectionResponse>, (StatusCode, String)> { ) -> Result<Json<DeleteCollectionResponse>, (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 // 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)), Ok(response) => Ok(Json(response)),
Err(e) => { Err(e) => {
tracing::error!("Error deleting collection: {}", e); tracing::error!("Error deleting collection: {}", e);
@ -27,10 +30,7 @@ pub async fn delete_collection(
format!("Collection not found: {}", e), format!("Collection not found: {}", e),
)) ))
} else if e.to_string().contains("permission") { } else if e.to_string().contains("permission") {
Err(( Err((StatusCode::FORBIDDEN, format!("Permission denied: {}", e)))
StatusCode::FORBIDDEN,
format!("Permission denied: {}", e),
))
} else { } else {
Err(( Err((
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,

View File

@ -1,12 +1,11 @@
use axum::{ use axum::{
extract::{Path, State}, extract::Path,
http::StatusCode, http::StatusCode,
Json, Extension, Json,
}; };
use handlers::collections::{get_collection_handler, CollectionState}; use handlers::collections::{get_collection_handler, CollectionState, GetCollectionRequest};
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;
use uuid::Uuid; use uuid::Uuid;
use axum::extract::Extension;
/// Get a collection by ID /// Get a collection by ID
/// ///
@ -15,8 +14,10 @@ pub async fn get_collection(
Extension(user): Extension<AuthenticatedUser>, Extension(user): Extension<AuthenticatedUser>,
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> Result<Json<CollectionState>, (StatusCode, String)> { ) -> Result<Json<CollectionState>, (StatusCode, String)> {
let request = GetCollectionRequest { id };
// Call the handler // Call the handler
match get_collection_handler(&user.id, &id).await { match get_collection_handler(&user.id, request).await {
Ok(collection) => Ok(Json(collection)), Ok(collection) => Ok(Json(collection)),
Err(e) => { Err(e) => {
tracing::error!("Error getting collection: {}", e); tracing::error!("Error getting collection: {}", e);

View File

@ -1,19 +1,19 @@
use axum::{ use axum::{
routing::get, routing::{get, post, put, delete},
Router, Router,
}; };
mod list_collections; mod list_collections;
// mod get_collection; mod get_collection;
// mod create_collection; mod create_collection;
// mod update_collection; mod update_collection;
// mod delete_collection; mod delete_collection;
pub fn router() -> Router { pub fn router() -> Router {
Router::new() Router::new()
.route("/", get(list_collections::list_collections)) .route("/", get(list_collections::list_collections))
// .route("/", post(create_collection::create_collection)) .route("/", post(create_collection::create_collection))
// .route("/:id", get(get_collection::get_collection)) .route("/:id", get(get_collection::get_collection))
// .route("/:id", put(update_collection::update_collection)) .route("/:id", put(update_collection::update_collection))
// .route("/:id", delete(delete_collection::delete_collection)) .route("/:id", delete(delete_collection::delete_collection))
} }

View File

@ -1,6 +1,6 @@
use axum::{ use axum::{
http::StatusCode, http::StatusCode,
Json, Extension, Json,
}; };
use handlers::collections::{update_collection_handler, UpdateCollectionRequest, CollectionState}; use handlers::collections::{update_collection_handler, UpdateCollectionRequest, CollectionState};
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;