From d7d4ab640193b6ff515f08a4eb3b356ebbb08671 Mon Sep 17 00:00:00 2001 From: dal Date: Fri, 18 Jul 2025 11:32:33 -0600 Subject: [PATCH] ok filtering the chats and hiding shareWithChannel for now --- .../handlers/src/chats/list_chats_handler.rs | 42 ++- .../handlers/tests/chats_list_filter_test.rs | 309 ++++++++++++++++++ apps/api/libs/handlers/tests/mod.rs | 1 + .../integrations/SlackIntegrations.tsx | 10 +- 4 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 apps/api/libs/handlers/tests/chats_list_filter_test.rs diff --git a/apps/api/libs/handlers/src/chats/list_chats_handler.rs b/apps/api/libs/handlers/src/chats/list_chats_handler.rs index 69f16cdca..554f58926 100644 --- a/apps/api/libs/handlers/src/chats/list_chats_handler.rs +++ b/apps/api/libs/handlers/src/chats/list_chats_handler.rs @@ -78,7 +78,7 @@ pub async fn list_chats_handler( request: ListChatsRequest, user: &AuthenticatedUser, ) -> Result> { - use database::schema::{asset_permissions, chats, messages, users}; + use database::schema::{asset_permissions, chats, messages, users, user_favorites}; let mut conn = get_pg_pool().get().await?; @@ -152,7 +152,29 @@ pub async fn list_chats_handler( // Get user's organization IDs let user_org_ids: Vec = user.organizations.iter().map(|org| org.id).collect(); + // Get user's favorited chat IDs + let favorited_chat_ids: Vec = if !request.admin_view { + asset_permissions::table + .filter(asset_permissions::identity_id.eq(user.id)) + .filter(asset_permissions::asset_type.eq(AssetType::Chat)) + .filter(asset_permissions::identity_type.eq(IdentityType::User)) + .filter(asset_permissions::deleted_at.is_null()) + .select(asset_permissions::asset_id) + .union( + user_favorites::table + .filter(user_favorites::user_id.eq(user.id)) + .filter(user_favorites::asset_type.eq(AssetType::Chat)) + .filter(user_favorites::deleted_at.is_null()) + .select(user_favorites::asset_id) + ) + .load::(&mut conn) + .await? + } else { + Vec::new() + }; + // Second query: Get workspace-shared chats that the user doesn't have direct access to + // but has either contributed to or favorited let workspace_shared_chats = if !request.admin_view && !user_org_ids.is_empty() { chats::table .inner_join(users::table.on(chats::created_by.eq(users::id))) @@ -173,6 +195,24 @@ pub async fn list_chats_handler( )) ) ) + // Only include if user has contributed (created or updated a message) or favorited + .filter( + // User has favorited the chat + chats::id.eq_any(&favorited_chat_ids) + .or( + // User has created a message in the chat + diesel::dsl::exists( + messages::table + .filter(messages::chat_id.eq(chats::id)) + .filter(messages::created_by.eq(user.id)) + .filter(messages::deleted_at.is_null()) + ) + ) + .or( + // User has updated the chat + chats::updated_by.eq(user.id) + ) + ) .filter( diesel::dsl::exists( messages::table diff --git a/apps/api/libs/handlers/tests/chats_list_filter_test.rs b/apps/api/libs/handlers/tests/chats_list_filter_test.rs new file mode 100644 index 000000000..0e79eaafb --- /dev/null +++ b/apps/api/libs/handlers/tests/chats_list_filter_test.rs @@ -0,0 +1,309 @@ +use anyhow::Result; +use database::{ + enums::{AssetType, WorkspaceSharing}, + models::{Chat, Message, UserFavorite}, + pool::get_pg_pool, + schema::{chats, messages, user_favorites}, + tests::common::db::{TestDb, TestSetup}, +}; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; +use handlers::chats::{list_chats_handler, ListChatsRequest}; +use middleware::UserOrganization; +use uuid::Uuid; + +#[tokio::test] +async fn test_workspace_shared_chats_filtered_without_contribution() -> Result<()> { + let setup = TestSetup::new(None).await?; + let mut conn = setup.db.diesel_conn().await?; + + // Create another user in the same organization + let other_user = setup.db.create_user("other@example.com", "Other User").await?; + + // Create a workspace-shared chat by the other user + let shared_chat = Chat { + id: Uuid::new_v4(), + title: "Shared Chat".to_string(), + organization_id: setup.organization.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + updated_by: other_user.id, + publicly_accessible: false, + publicly_enabled_by: None, + public_expiry_date: None, + most_recent_file_id: None, + most_recent_file_type: None, + most_recent_version_number: None, + workspace_sharing: WorkspaceSharing::CanView, + workspace_sharing_enabled_by: Some(other_user.id), + workspace_sharing_enabled_at: Some(chrono::Utc::now()), + }; + + diesel::insert_into(chats::table) + .values(&shared_chat) + .execute(&mut conn) + .await?; + + // Create a message in the chat by the other user + let message = Message { + id: Uuid::new_v4(), + request_message: Some("Test message".to_string()), + response_messages: serde_json::json!({}), + reasoning: serde_json::json!({}), + title: "Test".to_string(), + raw_llm_messages: serde_json::json!({}), + final_reasoning_message: None, + chat_id: shared_chat.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + feedback: None, + is_completed: true, + post_processing_message: None, + }; + + diesel::insert_into(messages::table) + .values(&message) + .execute(&mut conn) + .await?; + + // List chats for the test user - should NOT see the shared chat + let request = ListChatsRequest { + page: Some(1), + page_size: 10, + admin_view: false, + }; + + let auth_user = middleware::AuthenticatedUser { + id: setup.user.id, + email: setup.user.email.clone(), + name: setup.user.name.clone(), + organizations: vec![UserOrganization { + id: setup.organization.id, + name: setup.organization.name.clone(), + }], + }; + + let result = list_chats_handler(request, &auth_user).await?; + + // Should not see the shared chat since user hasn't contributed + assert_eq!(result.len(), 0); + + // Clean up + setup.db.cleanup().await?; + Ok(()) +} + +#[tokio::test] +async fn test_workspace_shared_chats_visible_with_contribution() -> Result<()> { + let setup = TestSetup::new(None).await?; + let mut conn = setup.db.diesel_conn().await?; + + // Create another user in the same organization + let other_user = setup.db.create_user("other@example.com", "Other User").await?; + + // Create a workspace-shared chat by the other user + let shared_chat = Chat { + id: Uuid::new_v4(), + title: "Shared Chat With Contribution".to_string(), + organization_id: setup.organization.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + updated_by: other_user.id, + publicly_accessible: false, + publicly_enabled_by: None, + public_expiry_date: None, + most_recent_file_id: None, + most_recent_file_type: None, + most_recent_version_number: None, + workspace_sharing: WorkspaceSharing::CanView, + workspace_sharing_enabled_by: Some(other_user.id), + workspace_sharing_enabled_at: Some(chrono::Utc::now()), + }; + + diesel::insert_into(chats::table) + .values(&shared_chat) + .execute(&mut conn) + .await?; + + // Create a message by the other user + let message1 = Message { + id: Uuid::new_v4(), + request_message: Some("Other user message".to_string()), + response_messages: serde_json::json!({}), + reasoning: serde_json::json!({}), + title: "Test".to_string(), + raw_llm_messages: serde_json::json!({}), + final_reasoning_message: None, + chat_id: shared_chat.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + feedback: None, + is_completed: true, + post_processing_message: None, + }; + + diesel::insert_into(messages::table) + .values(&message1) + .execute(&mut conn) + .await?; + + // Create a message by the test user (contribution) + let message2 = Message { + id: Uuid::new_v4(), + request_message: Some("Test user message".to_string()), + response_messages: serde_json::json!({}), + reasoning: serde_json::json!({}), + title: "Test".to_string(), + raw_llm_messages: serde_json::json!({}), + final_reasoning_message: None, + chat_id: shared_chat.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: setup.user.id, // Test user contributes + feedback: None, + is_completed: true, + post_processing_message: None, + }; + + diesel::insert_into(messages::table) + .values(&message2) + .execute(&mut conn) + .await?; + + // List chats for the test user - should see the shared chat + let request = ListChatsRequest { + page: Some(1), + page_size: 10, + admin_view: false, + }; + + let auth_user = middleware::AuthenticatedUser { + id: setup.user.id, + email: setup.user.email.clone(), + name: setup.user.name.clone(), + organizations: vec![UserOrganization { + id: setup.organization.id, + name: setup.organization.name.clone(), + }], + }; + + let result = list_chats_handler(request, &auth_user).await?; + + // Should see the shared chat since user has contributed + assert_eq!(result.len(), 1); + assert_eq!(result[0].name, "Shared Chat With Contribution"); + + // Clean up + setup.db.cleanup().await?; + Ok(()) +} + +#[tokio::test] +async fn test_workspace_shared_chats_visible_when_favorited() -> Result<()> { + let setup = TestSetup::new(None).await?; + let mut conn = setup.db.diesel_conn().await?; + + // Create another user in the same organization + let other_user = setup.db.create_user("other@example.com", "Other User").await?; + + // Create a workspace-shared chat by the other user + let shared_chat = Chat { + id: Uuid::new_v4(), + title: "Favorited Shared Chat".to_string(), + organization_id: setup.organization.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + updated_by: other_user.id, + publicly_accessible: false, + publicly_enabled_by: None, + public_expiry_date: None, + most_recent_file_id: None, + most_recent_file_type: None, + most_recent_version_number: None, + workspace_sharing: WorkspaceSharing::CanView, + workspace_sharing_enabled_by: Some(other_user.id), + workspace_sharing_enabled_at: Some(chrono::Utc::now()), + }; + + diesel::insert_into(chats::table) + .values(&shared_chat) + .execute(&mut conn) + .await?; + + // Create a message by the other user + let message = Message { + id: Uuid::new_v4(), + request_message: Some("Other user message".to_string()), + response_messages: serde_json::json!({}), + reasoning: serde_json::json!({}), + title: "Test".to_string(), + raw_llm_messages: serde_json::json!({}), + final_reasoning_message: None, + chat_id: shared_chat.id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: other_user.id, + feedback: None, + is_completed: true, + post_processing_message: None, + }; + + diesel::insert_into(messages::table) + .values(&message) + .execute(&mut conn) + .await?; + + // Add the chat to user's favorites + let favorite = UserFavorite { + user_id: setup.user.id, + asset_id: shared_chat.id, + asset_type: AssetType::Chat, + order_index: 0, + created_at: chrono::Utc::now(), + deleted_at: None, + }; + + diesel::insert_into(user_favorites::table) + .values(&favorite) + .execute(&mut conn) + .await?; + + // List chats for the test user - should see the shared chat + let request = ListChatsRequest { + page: Some(1), + page_size: 10, + admin_view: false, + }; + + let auth_user = middleware::AuthenticatedUser { + id: setup.user.id, + email: setup.user.email.clone(), + name: setup.user.name.clone(), + organizations: vec![UserOrganization { + id: setup.organization.id, + name: setup.organization.name.clone(), + }], + }; + + let result = list_chats_handler(request, &auth_user).await?; + + // Should see the shared chat since user has favorited it + assert_eq!(result.len(), 1); + assert_eq!(result[0].name, "Favorited Shared Chat"); + + // Clean up + setup.db.cleanup().await?; + Ok(()) +} \ No newline at end of file diff --git a/apps/api/libs/handlers/tests/mod.rs b/apps/api/libs/handlers/tests/mod.rs index d36e5a69c..0df92aef7 100644 --- a/apps/api/libs/handlers/tests/mod.rs +++ b/apps/api/libs/handlers/tests/mod.rs @@ -33,3 +33,4 @@ pub mod sharing; pub mod metrics; pub mod dashboards; pub mod collections; +pub mod chats_list_filter_test; diff --git a/apps/web/src/components/features/integrations/SlackIntegrations.tsx b/apps/web/src/components/features/integrations/SlackIntegrations.tsx index 88dc3f682..889f16cee 100644 --- a/apps/web/src/components/features/integrations/SlackIntegrations.tsx +++ b/apps/web/src/components/features/integrations/SlackIntegrations.tsx @@ -231,11 +231,11 @@ const SlackSharingPermissions = React.memo(() => { value: 'shareWithWorkspace', secondaryLabel: 'All workspace members will have access to any chat created from any channel.' }, - { - label: 'Channel', - value: 'shareWithChannel', - secondaryLabel: 'All channel members will have access to any chat created from that channel.' - }, + // { + // label: 'Channel', + // value: 'shareWithChannel', + // secondaryLabel: 'All channel members will have access to any chat created from that channel.' + // }, { label: 'None', value: 'noSharing',