diff --git a/migrations/20260601000001_cleanup_recent_invalid_item_ids.sql b/migrations/20260601000001_cleanup_recent_invalid_item_ids.sql new file mode 100644 index 00000000..13645c51 --- /dev/null +++ b/migrations/20260601000001_cleanup_recent_invalid_item_ids.sql @@ -0,0 +1,10 @@ +-- Remove rows from auth.user_recent_files and auth.user_favorites whose +-- item_id is not a valid UUID (e.g. composite "uuid1_uuid2" values written +-- by a previous code path that joined owner_id and resource_id with '_'). +-- These rows would cause a cast failure on `item_id::UUID` in list queries. + +DELETE FROM auth.user_recent_files +WHERE item_id !~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; + +DELETE FROM auth.user_favorites +WHERE item_id !~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; diff --git a/src/interfaces/api/handlers/recent_handler.rs b/src/interfaces/api/handlers/recent_handler.rs index 62f72126..5ce50631 100644 --- a/src/interfaces/api/handlers/recent_handler.rs +++ b/src/interfaces/api/handlers/recent_handler.rs @@ -21,6 +21,7 @@ use crate::application::ports::recent_ports::RecentItemsUseCase; use crate::application::services::recent_service::RecentService; use crate::interfaces::errors::AppError; use crate::interfaces::middleware::auth::AuthUser; +use uuid::Uuid; /// Query parameters for getting recent items #[derive(Deserialize)] @@ -83,7 +84,7 @@ pub async fn get_recent_items( pub async fn record_item_access( State(recent_service): State>, auth_user: AuthUser, - Path((item_type, item_id)): Path<(String, String)>, + Path((item_type, item_id)): Path<(String, Uuid)>, ) -> impl IntoResponse { let user_id = auth_user.id; @@ -99,7 +100,7 @@ pub async fn record_item_access( } match recent_service - .record_item_access(user_id, &item_id, &item_type) + .record_item_access(user_id, &item_id.to_string(), &item_type) .await { Ok(_) => { @@ -143,12 +144,12 @@ pub async fn record_item_access( pub async fn remove_from_recent( State(recent_service): State>, auth_user: AuthUser, - Path((item_type, item_id)): Path<(String, String)>, + Path((item_type, item_id)): Path<(String, Uuid)>, ) -> impl IntoResponse { let user_id = auth_user.id; match recent_service - .remove_from_recent(user_id, &item_id, &item_type) + .remove_from_recent(user_id, &item_id.to_string(), &item_type) .await { Ok(removed) => {