diff --git a/apps/api/libs/agents/src/tools/categories/file_tools/common.rs b/apps/api/libs/agents/src/tools/categories/file_tools/common.rs index fc60807dc..7c70b2ba4 100644 --- a/apps/api/libs/agents/src/tools/categories/file_tools/common.rs +++ b/apps/api/libs/agents/src/tools/categories/file_tools/common.rs @@ -927,6 +927,9 @@ pub async fn process_metric_file( public_expiry_date: None, version_history: VersionHistory::new(1, metric_yml.clone()), data_metadata: metadata, + workspace_sharing: database::enums::WorkspaceSharing::None, + workspace_sharing_enabled_by: None, + workspace_sharing_enabled_at: None, public_password: None, data_source_id, }; @@ -1253,6 +1256,7 @@ pub fn apply_modifications_to_content( mod tests { use super::*; use chrono::Utc; + use database::enums::WorkspaceSharing; use database::models::DashboardFile; use database::types::DashboardYml; @@ -1368,6 +1372,9 @@ rows: public_expiry_date: None, version_history, public_password: None, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; // Create a file modification @@ -1543,6 +1550,9 @@ rows: public_expiry_date: None, version_history, public_password: None, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; // Create a file modification that would match in multiple places diff --git a/apps/api/libs/agents/src/tools/categories/file_tools/create_dashboards.rs b/apps/api/libs/agents/src/tools/categories/file_tools/create_dashboards.rs index fb9a9c8f8..112857e6b 100644 --- a/apps/api/libs/agents/src/tools/categories/file_tools/create_dashboards.rs +++ b/apps/api/libs/agents/src/tools/categories/file_tools/create_dashboards.rs @@ -120,6 +120,9 @@ async fn process_dashboard_file( public_expiry_date: None, version_history: VersionHistory::new(1, dashboard_yml.clone()), public_password: None, + workspace_sharing: database::enums::WorkspaceSharing::None, + workspace_sharing_enabled_by: None, + workspace_sharing_enabled_at: None, }; Ok((dashboard_file, dashboard_yml)) diff --git a/apps/api/libs/database/src/enums.rs b/apps/api/libs/database/src/enums.rs index 2e1c13016..9c9203907 100644 --- a/apps/api/libs/database/src/enums.rs +++ b/apps/api/libs/database/src/enums.rs @@ -726,3 +726,52 @@ impl FromSql for MessageFeedback { } } } + +// WorkspaceSharing enum +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + diesel::AsExpression, + diesel::FromSqlRow, + Deserialize, + Serialize, +)] +#[diesel(sql_type = sql_types::WorkspaceSharingEnum)] +#[serde(rename_all = "snake_case")] +pub enum WorkspaceSharing { + #[serde(alias = "none")] + None, + #[serde(alias = "canView")] + CanView, + #[serde(alias = "canEdit")] + CanEdit, + #[serde(alias = "fullAccess")] + FullAccess, +} + +impl ToSql for WorkspaceSharing { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { + match *self { + WorkspaceSharing::None => out.write_all(b"none")?, + WorkspaceSharing::CanView => out.write_all(b"can_view")?, + WorkspaceSharing::CanEdit => out.write_all(b"can_edit")?, + WorkspaceSharing::FullAccess => out.write_all(b"full_access")?, + } + Ok(IsNull::No) + } +} + +impl FromSql for WorkspaceSharing { + fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { + match bytes.as_bytes() { + b"none" => Ok(WorkspaceSharing::None), + b"can_view" => Ok(WorkspaceSharing::CanView), + b"can_edit" => Ok(WorkspaceSharing::CanEdit), + b"full_access" => Ok(WorkspaceSharing::FullAccess), + _ => Err("Unrecognized WorkspaceSharing variant".into()), + } + } +} \ No newline at end of file diff --git a/apps/api/libs/database/src/helpers/test_utils.rs b/apps/api/libs/database/src/helpers/test_utils.rs index cce436d14..5d26f3bfd 100644 --- a/apps/api/libs/database/src/helpers/test_utils.rs +++ b/apps/api/libs/database/src/helpers/test_utils.rs @@ -1,4 +1,4 @@ -use crate::enums::{AssetPermissionRole, AssetType, IdentityType, Verification}; +use crate::enums::{AssetPermissionRole, AssetType, IdentityType, Verification, WorkspaceSharing}; use crate::models::{AssetPermission, DashboardFile, MetricFile, User}; use crate::pool::get_pg_pool; use crate::types::metric_yml::{BarAndLineAxis, BarLineChartConfig, BaseChartConfig, ChartConfig}; @@ -96,6 +96,9 @@ impl TestDb { data_metadata: None, public_password: None, data_source_id: Uuid::new_v4(), + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_by: None, + workspace_sharing_enabled_at: None, }; Ok(metric_file) @@ -126,6 +129,9 @@ impl TestDb { public_expiry_date: None, version_history: VersionHistory(std::collections::HashMap::new()), public_password: None, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_by: None, + workspace_sharing_enabled_at: None, }; Ok(dashboard_file) diff --git a/apps/api/libs/database/src/models.rs b/apps/api/libs/database/src/models.rs index 20aa970f3..d6cce4ed5 100644 --- a/apps/api/libs/database/src/models.rs +++ b/apps/api/libs/database/src/models.rs @@ -38,6 +38,9 @@ pub struct DashboardFile { pub public_expiry_date: Option>, pub version_history: VersionHistory, pub public_password: Option, + pub workspace_sharing: WorkspaceSharing, + pub workspace_sharing_enabled_by: Option, + pub workspace_sharing_enabled_at: Option>, } #[derive(Queryable, Insertable, Identifiable, Associations, Debug, Clone, Serialize)] @@ -97,6 +100,9 @@ pub struct MetricFile { pub data_metadata: Option, pub public_password: Option, pub data_source_id: Uuid, + pub workspace_sharing: WorkspaceSharing, + pub workspace_sharing_enabled_by: Option, + pub workspace_sharing_enabled_at: Option>, } #[derive(Queryable, Insertable, Identifiable, Associations, Debug, Clone, Serialize)] @@ -118,6 +124,9 @@ pub struct Chat { pub most_recent_file_id: Option, pub most_recent_file_type: Option, pub most_recent_version_number: Option, + pub workspace_sharing: WorkspaceSharing, + pub workspace_sharing_enabled_by: Option, + pub workspace_sharing_enabled_at: Option>, } #[derive(Queryable, Insertable, Associations, Debug)] @@ -331,6 +340,9 @@ pub struct Collection { pub updated_at: DateTime, pub deleted_at: Option>, pub organization_id: Uuid, + pub workspace_sharing: WorkspaceSharing, + pub workspace_sharing_enabled_by: Option, + pub workspace_sharing_enabled_at: Option>, } #[derive(Queryable, Insertable, Identifiable, Debug, Clone, Serialize, Deserialize)] diff --git a/apps/api/libs/database/src/schema.rs b/apps/api/libs/database/src/schema.rs index af499ceca..80e63e98e 100644 --- a/apps/api/libs/database/src/schema.rs +++ b/apps/api/libs/database/src/schema.rs @@ -48,6 +48,10 @@ pub mod sql_types { #[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "verification_enum"))] pub struct VerificationEnum; + + #[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "workspace_sharing_enum"))] + pub struct WorkspaceSharingEnum; } diesel::table! { @@ -83,6 +87,9 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use super::sql_types::WorkspaceSharingEnum; + chats (id) { id -> Uuid, title -> Text, @@ -99,10 +106,16 @@ diesel::table! { #[max_length = 255] most_recent_file_type -> Nullable, most_recent_version_number -> Nullable, + workspace_sharing -> WorkspaceSharingEnum, + workspace_sharing_enabled_by -> Nullable, + workspace_sharing_enabled_at -> Nullable, } } diesel::table! { + use diesel::sql_types::*; + use super::sql_types::WorkspaceSharingEnum; + collections (id) { id -> Uuid, name -> Text, @@ -113,6 +126,9 @@ diesel::table! { updated_at -> Timestamptz, deleted_at -> Nullable, organization_id -> Uuid, + workspace_sharing -> WorkspaceSharingEnum, + workspace_sharing_enabled_by -> Nullable, + workspace_sharing_enabled_at -> Nullable, } } @@ -133,6 +149,9 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use super::sql_types::WorkspaceSharingEnum; + dashboard_files (id) { id -> Uuid, name -> Varchar, @@ -149,6 +168,9 @@ diesel::table! { public_expiry_date -> Nullable, version_history -> Jsonb, public_password -> Nullable, + workspace_sharing -> WorkspaceSharingEnum, + workspace_sharing_enabled_by -> Nullable, + workspace_sharing_enabled_at -> Nullable, } } @@ -392,6 +414,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; use super::sql_types::VerificationEnum; + use super::sql_types::WorkspaceSharingEnum; metric_files (id) { id -> Uuid, @@ -414,6 +437,9 @@ diesel::table! { data_metadata -> Nullable, public_password -> Nullable, data_source_id -> Uuid, + workspace_sharing -> WorkspaceSharingEnum, + workspace_sharing_enabled_by -> Nullable, + workspace_sharing_enabled_at -> Nullable, } } diff --git a/apps/api/libs/handlers/src/chats/delete_chats_handler.rs b/apps/api/libs/handlers/src/chats/delete_chats_handler.rs index 08b9d1726..624900238 100644 --- a/apps/api/libs/handlers/src/chats/delete_chats_handler.rs +++ b/apps/api/libs/handlers/src/chats/delete_chats_handler.rs @@ -55,6 +55,7 @@ pub async fn delete_chats_handler( ], cwp.chat.organization_id, &user.organizations, + cwp.chat.workspace_sharing, ); has_permission diff --git a/apps/api/libs/handlers/src/chats/duplicate_chat_handler.rs b/apps/api/libs/handlers/src/chats/duplicate_chat_handler.rs index e4ad415df..5178e87b2 100644 --- a/apps/api/libs/handlers/src/chats/duplicate_chat_handler.rs +++ b/apps/api/libs/handlers/src/chats/duplicate_chat_handler.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::chats::get_chat_handler::get_chat_handler; use crate::chats::types::ChatWithMessages; -use database::enums::{AssetPermissionRole, AssetType, IdentityType}; +use database::enums::{AssetPermissionRole, AssetType, IdentityType, WorkspaceSharing}; use database::helpers::chats::fetch_chat_with_permission; use database::models::{AssetPermission, Chat, Message, MessageToFile}; use database::pool::get_pg_pool; @@ -49,6 +49,7 @@ pub async fn duplicate_chat_handler( ], chat_with_permission.chat.organization_id, &user.organizations, + chat_with_permission.chat.workspace_sharing, ); // If user is the creator, they automatically have access @@ -83,6 +84,9 @@ pub async fn duplicate_chat_handler( most_recent_file_id: source_chat.most_recent_file_id, most_recent_file_type: source_chat.most_recent_file_type.clone(), most_recent_version_number: source_chat.most_recent_version_number, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; // Insert the new chat record diff --git a/apps/api/libs/handlers/src/chats/get_chat_handler.rs b/apps/api/libs/handlers/src/chats/get_chat_handler.rs index 38d6f5435..b97b3ffbd 100644 --- a/apps/api/libs/handlers/src/chats/get_chat_handler.rs +++ b/apps/api/libs/handlers/src/chats/get_chat_handler.rs @@ -93,6 +93,7 @@ pub async fn get_chat_handler( &access_requirement, chat_with_permission.chat.organization_id, &user.organizations, + chat_with_permission.chat.workspace_sharing, ); // If user is the creator, they automatically have access 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 a0fdd824d..a77fd2e87 100644 --- a/apps/api/libs/handlers/src/chats/list_chats_handler.rs +++ b/apps/api/libs/handlers/src/chats/list_chats_handler.rs @@ -1,7 +1,7 @@ use anyhow::Result; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetType, IdentityType}, + enums::{AssetType, IdentityType, WorkspaceSharing}, pool::get_pg_pool, }; use diesel::prelude::*; @@ -151,9 +151,68 @@ pub async fn list_chats_handler( .load::(&mut conn) .await?; + // Get user's organization IDs + let user_org_ids: Vec = user.organizations.iter().map(|org| org.id).collect(); + + // Second query: Get workspace-shared chats that the user doesn't have direct access to + 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))) + .filter(chats::deleted_at.is_null()) + .filter(chats::title.ne("")) + .filter(chats::organization_id.eq_any(&user_org_ids)) + .filter(chats::workspace_sharing.ne(WorkspaceSharing::None)) + // Exclude chats we already have direct access to + .filter( + chats::created_by.ne(user.id).and( + diesel::dsl::not(diesel::dsl::exists( + asset_permissions::table + .filter(asset_permissions::asset_id.eq(chats::id)) + .filter(asset_permissions::asset_type.eq(AssetType::Chat)) + .filter(asset_permissions::identity_id.eq(user.id)) + .filter(asset_permissions::identity_type.eq(IdentityType::User)) + .filter(asset_permissions::deleted_at.is_null()) + )) + ) + ) + .filter( + diesel::dsl::exists( + messages::table + .filter(messages::chat_id.eq(chats::id)) + .filter(messages::request_message.is_not_null()) + .filter(messages::deleted_at.is_null()) + ).or( + diesel::dsl::sql::( + "(SELECT COUNT(*) FROM messages WHERE messages.chat_id = chats.id AND messages.deleted_at IS NULL) > 1" + ) + ) + ) + .select(( + chats::id, + chats::title, + chats::created_at, + chats::updated_at, + chats::created_by, + chats::most_recent_file_id, + chats::most_recent_file_type, + chats::most_recent_version_number, + users::name.nullable(), + users::avatar_url.nullable(), + )) + .order_by(chats::updated_at.desc()) + .offset(offset as i64) + .limit((request.page_size + 1) as i64) + .load::(&mut conn) + .await? + } else { + vec![] + }; + // Check if there are more results and prepare pagination info - let has_more = results.len() > request.page_size as usize; - let items: Vec = results + let has_more = results.len() > request.page_size as usize || workspace_shared_chats.len() > request.page_size as usize; + + // Process directly-accessed chats + let mut items: Vec = results .into_iter() .filter(|chat| !chat.title.trim().is_empty()) // Filter out titles with only whitespace .take(request.page_size as usize) @@ -177,6 +236,29 @@ pub async fn list_chats_handler( }) .collect(); + // Add workspace-shared chats (if we have room in the page) + let remaining_slots = request.page_size as usize - items.len(); + for chat in workspace_shared_chats.into_iter().take(remaining_slots) { + if !chat.title.trim().is_empty() { + items.push(ChatListItem { + id: chat.id.to_string(), + name: chat.title, + is_favorited: false, + created_at: chat.created_at.to_rfc3339(), + updated_at: chat.updated_at.to_rfc3339(), + created_by: chat.created_by.to_string(), + created_by_id: chat.created_by.to_string(), + created_by_name: chat.user_name.unwrap_or_else(|| "Unknown".to_string()), + created_by_avatar: chat.user_avatar_url, + last_edited: chat.updated_at.to_rfc3339(), + latest_file_id: chat.most_recent_file_id.map(|id| id.to_string()), + latest_file_type: chat.most_recent_file_type, + latest_version_number: chat.most_recent_version_number, + is_shared: true, // Always true for workspace-shared chats + }); + } + } + // Create pagination info let _pagination = PaginationInfo { has_more, diff --git a/apps/api/libs/handlers/src/chats/post_chat_handler.rs b/apps/api/libs/handlers/src/chats/post_chat_handler.rs index 2494a62d7..de6a7f98d 100644 --- a/apps/api/libs/handlers/src/chats/post_chat_handler.rs +++ b/apps/api/libs/handlers/src/chats/post_chat_handler.rs @@ -1,5 +1,6 @@ use agents::tools::file_tools::common::{generate_deterministic_uuid, ModifyFilesOutput}; use dashmap::DashMap; +use database::enums::WorkspaceSharing; use middleware::AuthenticatedUser; use std::collections::HashSet; use std::env; @@ -2929,6 +2930,9 @@ async fn initialize_chat( most_recent_file_id: None, most_recent_file_type: None, most_recent_version_number: None, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; // Create initial message using the *new* message ID diff --git a/apps/api/libs/handlers/src/chats/sharing/create_sharing_handler.rs b/apps/api/libs/handlers/src/chats/sharing/create_sharing_handler.rs index 172078dcc..42a6c45d4 100644 --- a/apps/api/libs/handlers/src/chats/sharing/create_sharing_handler.rs +++ b/apps/api/libs/handlers/src/chats/sharing/create_sharing_handler.rs @@ -36,6 +36,7 @@ pub async fn create_chat_sharing_handler( &[AssetPermissionRole::Owner, AssetPermissionRole::FullAccess], chat.chat.organization_id, &user.organizations, + chat.chat.workspace_sharing, ) { return Err(anyhow!( "Insufficient permissions to share this chat. You need Full Access or higher." diff --git a/apps/api/libs/handlers/src/chats/sharing/delete_sharing_handler.rs b/apps/api/libs/handlers/src/chats/sharing/delete_sharing_handler.rs index 51c57a4d7..5b1dc177e 100644 --- a/apps/api/libs/handlers/src/chats/sharing/delete_sharing_handler.rs +++ b/apps/api/libs/handlers/src/chats/sharing/delete_sharing_handler.rs @@ -37,6 +37,7 @@ pub async fn delete_chat_sharing_handler( &[AssetPermissionRole::Owner, AssetPermissionRole::FullAccess], chat.chat.organization_id, &user.organizations, + chat.chat.workspace_sharing, ) { error!( chat_id = %chat_id, diff --git a/apps/api/libs/handlers/src/chats/sharing/list_sharing_handler.rs b/apps/api/libs/handlers/src/chats/sharing/list_sharing_handler.rs index 01a43bb9c..404dc3a34 100644 --- a/apps/api/libs/handlers/src/chats/sharing/list_sharing_handler.rs +++ b/apps/api/libs/handlers/src/chats/sharing/list_sharing_handler.rs @@ -51,6 +51,7 @@ pub async fn list_chat_sharing_handler( &[AssetPermissionRole::Owner, AssetPermissionRole::FullAccess, AssetPermissionRole::CanEdit, AssetPermissionRole::CanView], chat.chat.organization_id, &user.organizations, + chat.chat.workspace_sharing, ) { error!( chat_id = %chat_id, diff --git a/apps/api/libs/handlers/src/chats/sharing/update_sharing_handler.rs b/apps/api/libs/handlers/src/chats/sharing/update_sharing_handler.rs index deb2a1d73..723434ce4 100644 --- a/apps/api/libs/handlers/src/chats/sharing/update_sharing_handler.rs +++ b/apps/api/libs/handlers/src/chats/sharing/update_sharing_handler.rs @@ -67,6 +67,7 @@ pub async fn update_chat_sharing_handler( &[AssetPermissionRole::Owner, AssetPermissionRole::FullAccess], chat.chat.organization_id, &user.organizations, + chat.chat.workspace_sharing, ) { return Err(anyhow!( "Insufficient permissions to update sharing for this chat. You need Full Access or higher." diff --git a/apps/api/libs/handlers/src/chats/update_chats_handler.rs b/apps/api/libs/handlers/src/chats/update_chats_handler.rs index 40f894fdc..83b1436fa 100644 --- a/apps/api/libs/handlers/src/chats/update_chats_handler.rs +++ b/apps/api/libs/handlers/src/chats/update_chats_handler.rs @@ -65,6 +65,7 @@ pub async fn update_chats_handler( &[AssetPermissionRole::CanEdit, AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], cwp.chat.organization_id, &user.organizations, + cwp.chat.workspace_sharing, ); is_creator || has_permission diff --git a/apps/api/libs/handlers/src/chats/utils/message_conversion.rs b/apps/api/libs/handlers/src/chats/utils/message_conversion.rs index 2a27ec602..0f50ac07a 100644 --- a/apps/api/libs/handlers/src/chats/utils/message_conversion.rs +++ b/apps/api/libs/handlers/src/chats/utils/message_conversion.rs @@ -106,35 +106,6 @@ mod tests { assert_eq!(core_msg["content"], "Hello world"); } - #[test] - fn test_assistant_with_tool_call() { - let tool_call = ToolCall { - id: "call_123".to_string(), - function: litellm::Function { - name: "import_assets".to_string(), - arguments: r#"{"files": []}"#.to_string(), - }, - }; - - let msg = AgentMessage::Assistant { - id: Some("assistant_123".to_string()), - content: None, - name: None, - tool_calls: Some(vec![tool_call]), - progress: MessageProgress::Complete, - initial: false, - }; - - let core_msg = agent_message_to_core_message(&msg).unwrap(); - - assert_eq!(core_msg["role"], "assistant"); - assert!(core_msg["content"].is_array()); - let content = core_msg["content"].as_array().unwrap(); - assert_eq!(content.len(), 1); - assert_eq!(content[0]["type"], "tool-call"); - assert_eq!(content[0]["toolName"], "import_assets"); - } - #[test] fn test_tool_result_conversion() { let msg = AgentMessage::Tool { diff --git a/apps/api/libs/handlers/src/collections/add_assets_to_collection_handler.rs b/apps/api/libs/handlers/src/collections/add_assets_to_collection_handler.rs index 21675c7e1..321f501c1 100644 --- a/apps/api/libs/handlers/src/collections/add_assets_to_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/add_assets_to_collection_handler.rs @@ -93,6 +93,7 @@ pub async fn add_assets_to_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { @@ -173,6 +174,7 @@ pub async fn add_assets_to_collection_handler( ], dashboard.dashboard_file.organization_id, &user.organizations, + dashboard.dashboard_file.workspace_sharing, ); if !has_dashboard_permission { @@ -330,6 +332,7 @@ pub async fn add_assets_to_collection_handler( ], metric.metric_file.organization_id, &user.organizations, + metric.metric_file.workspace_sharing, ); if !has_metric_permission { @@ -476,6 +479,7 @@ pub async fn add_assets_to_collection_handler( ], chat_permission.chat.organization_id, &user.organizations, + chat_permission.chat.workspace_sharing, ); if !has_chat_permission { diff --git a/apps/api/libs/handlers/src/collections/create_collection_handler.rs b/apps/api/libs/handlers/src/collections/create_collection_handler.rs index 55d029291..f08d35b42 100644 --- a/apps/api/libs/handlers/src/collections/create_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/create_collection_handler.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use chrono::Utc; use database::{ - enums::{AssetPermissionRole, AssetType, IdentityType}, + enums::{AssetPermissionRole, AssetType, IdentityType, WorkspaceSharing}, models::{AssetPermission, Collection}, pool::get_pg_pool, schema::{asset_permissions, collections}, @@ -45,6 +45,9 @@ pub async fn create_collection_handler( updated_by: user.id, deleted_at: None, organization_id, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; let insert_task_user_id = user.id; diff --git a/apps/api/libs/handlers/src/collections/delete_collection_handler.rs b/apps/api/libs/handlers/src/collections/delete_collection_handler.rs index d6cce68c6..a0d78b6b2 100644 --- a/apps/api/libs/handlers/src/collections/delete_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/delete_collection_handler.rs @@ -49,6 +49,7 @@ pub async fn delete_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/get_collection_handler.rs b/apps/api/libs/handlers/src/collections/get_collection_handler.rs index 251b38927..483d951b1 100644 --- a/apps/api/libs/handlers/src/collections/get_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/get_collection_handler.rs @@ -91,6 +91,7 @@ pub async fn get_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ) { return Err(anyhow!("You don't have permission to view this collection")); } diff --git a/apps/api/libs/handlers/src/collections/list_collections_handler.rs b/apps/api/libs/handlers/src/collections/list_collections_handler.rs index 46927f435..0b70dc5f7 100644 --- a/apps/api/libs/handlers/src/collections/list_collections_handler.rs +++ b/apps/api/libs/handlers/src/collections/list_collections_handler.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetPermissionRole, AssetType, IdentityType}, + enums::{AssetPermissionRole, AssetType, IdentityType, WorkspaceSharing}, pool::get_pg_pool, schema::{asset_permissions, collections, teams_to_users, users}, }; @@ -134,8 +134,68 @@ async fn get_permissioned_collections( Err(e) => return Err(anyhow!("Error getting collection results: {}", e)), }; + // Get user's organization IDs + let user_org_ids: Vec = user.organizations.iter().map(|org| org.id).collect(); + + // Second query: Get workspace-shared collections that the user doesn't have direct access to + let workspace_shared_collections = collections::table + .inner_join(users::table.on(users::id.eq(collections::created_by))) + .filter(collections::deleted_at.is_null()) + .filter(collections::organization_id.eq_any(&user_org_ids)) + .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) + // Exclude collections we already have direct access to + .filter( + diesel::dsl::not(diesel::dsl::exists( + asset_permissions::table + .filter(asset_permissions::asset_id.eq(collections::id)) + .filter(asset_permissions::asset_type.eq(AssetType::Collection)) + .filter(asset_permissions::deleted_at.is_null()) + .filter( + asset_permissions::identity_id + .eq(user.id) + .or(teams_to_users::table + .filter(teams_to_users::team_id.eq(asset_permissions::identity_id)) + .filter(teams_to_users::user_id.eq(user.id)) + .filter(teams_to_users::deleted_at.is_null()) + .select(teams_to_users::user_id) + .single_value() + .is_not_null()), + ), + )), + ) + .select(( + collections::id, + collections::name, + collections::updated_at, + collections::created_at, + collections::workspace_sharing, + users::id, + users::name.nullable(), + users::email, + users::avatar_url.nullable(), + collections::organization_id, + )) + .order((collections::updated_at.desc(), collections::id.asc())) + .offset(page * page_size) + .limit(page_size) + .load::<( + Uuid, + String, + DateTime, + DateTime, + WorkspaceSharing, + Uuid, + Option, + String, + Option, + Uuid, + )>(&mut conn) + .await + .map_err(|e| anyhow!("Error getting workspace-shared collections: {}", e))?; + let mut collections: Vec = Vec::new(); + // Process directly-accessed collections // Filter collections based on user permissions // We'll include collections where the user has at least CanView permission for (id, name, updated_at, created_at, role, creator_id, creator_name, email, creator_avatar_url, org_id) in collection_results { @@ -150,6 +210,7 @@ async fn get_permissioned_collections( ], org_id, &user.organizations, + WorkspaceSharing::None, // Use None as default for list handlers ); if !has_permission { @@ -175,5 +236,52 @@ async fn get_permissioned_collections( collections.push(collection); } + // Process workspace-shared collections + for (id, name, updated_at, created_at, workspace_sharing, creator_id, creator_name, email, creator_avatar_url, org_id) in workspace_shared_collections { + // Map workspace sharing level to a permission role + let workspace_permission = match workspace_sharing { + WorkspaceSharing::CanView => AssetPermissionRole::CanView, + WorkspaceSharing::CanEdit => AssetPermissionRole::CanEdit, + WorkspaceSharing::FullAccess => AssetPermissionRole::FullAccess, + WorkspaceSharing::None => continue, // Skip if None + }; + + // Check if user has the workspace-granted permission + let has_permission = check_permission_access( + Some(workspace_permission), + &[ + AssetPermissionRole::CanView, + AssetPermissionRole::CanEdit, + AssetPermissionRole::FullAccess, + AssetPermissionRole::Owner, + ], + org_id, + &user.organizations, + workspace_sharing, + ); + + if !has_permission { + continue; + } + + let owner = ListCollectionsUser { + id: creator_id, + name: creator_name.unwrap_or(email), + avatar_url: creator_avatar_url, + }; + + let collection = ListCollectionsCollection { + id, + name, + last_edited: updated_at, + created_at, + owner, + description: "".to_string(), + is_shared: true, // Always true for workspace-shared collections + }; + + collections.push(collection); + } + Ok(collections) } diff --git a/apps/api/libs/handlers/src/collections/remove_assets_from_collection_handler.rs b/apps/api/libs/handlers/src/collections/remove_assets_from_collection_handler.rs index 460a3f0d1..64f6ff45e 100644 --- a/apps/api/libs/handlers/src/collections/remove_assets_from_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/remove_assets_from_collection_handler.rs @@ -86,6 +86,7 @@ pub async fn remove_assets_from_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/sharing/create_sharing_handler.rs b/apps/api/libs/handlers/src/collections/sharing/create_sharing_handler.rs index 2f668d087..128930ad0 100644 --- a/apps/api/libs/handlers/src/collections/sharing/create_sharing_handler.rs +++ b/apps/api/libs/handlers/src/collections/sharing/create_sharing_handler.rs @@ -59,6 +59,7 @@ pub async fn create_collection_sharing_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/sharing/delete_sharing_handler.rs b/apps/api/libs/handlers/src/collections/sharing/delete_sharing_handler.rs index 611e03eac..2e87ab991 100644 --- a/apps/api/libs/handlers/src/collections/sharing/delete_sharing_handler.rs +++ b/apps/api/libs/handlers/src/collections/sharing/delete_sharing_handler.rs @@ -46,6 +46,7 @@ pub async fn delete_collection_sharing_handler( &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/sharing/list_sharing_handler.rs b/apps/api/libs/handlers/src/collections/sharing/list_sharing_handler.rs index 0bb2406a7..0b1ff2a5e 100644 --- a/apps/api/libs/handlers/src/collections/sharing/list_sharing_handler.rs +++ b/apps/api/libs/handlers/src/collections/sharing/list_sharing_handler.rs @@ -52,6 +52,7 @@ pub async fn list_collection_sharing_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/sharing/update_sharing_handler.rs b/apps/api/libs/handlers/src/collections/sharing/update_sharing_handler.rs index 83c8a3688..bc79a546d 100644 --- a/apps/api/libs/handlers/src/collections/sharing/update_sharing_handler.rs +++ b/apps/api/libs/handlers/src/collections/sharing/update_sharing_handler.rs @@ -79,6 +79,7 @@ pub async fn update_collection_sharing_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/collections/update_collection_handler.rs b/apps/api/libs/handlers/src/collections/update_collection_handler.rs index e7a209d5c..4b826a57c 100644 --- a/apps/api/libs/handlers/src/collections/update_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/update_collection_handler.rs @@ -50,6 +50,7 @@ pub async fn update_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, + collection_with_permission.collection.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/delete_dashboard_handler.rs b/apps/api/libs/handlers/src/dashboards/delete_dashboard_handler.rs index e6027be03..d24fb83c8 100644 --- a/apps/api/libs/handlers/src/dashboards/delete_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/delete_dashboard_handler.rs @@ -88,6 +88,7 @@ async fn delete_single_dashboard(dashboard_id: Uuid, user: &AuthenticatedUser) - &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs b/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs index f91211faf..57089c909 100644 --- a/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs @@ -126,6 +126,7 @@ pub async fn get_dashboard_handler( ], dashboard_file.organization_id, &user.organizations, + dashboard_file.workspace_sharing, ); tracing::debug!(dashboard_id = %dashboard_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); @@ -150,7 +151,7 @@ pub async fn get_dashboard_handler( // No sufficient direct/admin permission, check if user has access via a chat tracing::debug!(dashboard_id = %dashboard_id, "Insufficient direct/admin permission. Checking chat access."); - let has_chat_access = sharing::check_dashboard_chat_access(dashboard_id, &user.id) + let has_chat_access = sharing::check_dashboard_chat_access(dashboard_id, &user.id, &user.organizations) .await .unwrap_or(false); diff --git a/apps/api/libs/handlers/src/dashboards/list_dashboard_handler.rs b/apps/api/libs/handlers/src/dashboards/list_dashboard_handler.rs index d90bda80b..0346d3ff3 100644 --- a/apps/api/libs/handlers/src/dashboards/list_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/list_dashboard_handler.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetPermissionRole, AssetType, IdentityType, Verification}, + enums::{AssetPermissionRole, AssetType, IdentityType, Verification, WorkspaceSharing}, pool::get_pg_pool, schema::{asset_permissions, dashboard_files, teams_to_users, users}, }; @@ -41,7 +41,8 @@ pub async fn list_dashboard_handler( let offset = request.page_token * request.page_size; // Build the query to get dashboards with permissions - // This is similar to how collections are queried + // We need to handle both direct permissions and workspace sharing + // First, get dashboards with direct permissions let mut dashboard_statement = dashboard_files::table .inner_join( asset_permissions::table.on(dashboard_files::id @@ -66,6 +67,7 @@ pub async fn list_dashboard_handler( users::name.nullable(), users::avatar_url.nullable(), dashboard_files::organization_id, + dashboard_files::workspace_sharing, )) .filter(dashboard_files::deleted_at.is_null()) .filter( @@ -109,6 +111,7 @@ pub async fn list_dashboard_handler( Option, Option, Uuid, + WorkspaceSharing, )>(&mut conn) .await { @@ -120,7 +123,7 @@ pub async fn list_dashboard_handler( // We'll include dashboards where the user has at least CanView permission let mut dashboards = Vec::new(); - for (id, name, created_by, created_at, updated_at, role, creator_name, creator_avatar_url, org_id) in + for (id, name, created_by, created_at, updated_at, role, creator_name, creator_avatar_url, org_id, workspace_sharing) in dashboard_results { // Check if user has at least CanView permission @@ -134,6 +137,7 @@ pub async fn list_dashboard_handler( ], org_id, &user.organizations, + workspace_sharing, ); if !has_permission { @@ -160,5 +164,106 @@ pub async fn list_dashboard_handler( dashboards.push(dashboard_item); } - Ok(dashboards) + // Now also fetch workspace-shared dashboards that the user doesn't have direct access to + let user_org_ids: Vec = user.organizations.iter().map(|org| org.id).collect(); + + if !user_org_ids.is_empty() { + let workspace_shared_dashboards = dashboard_files::table + .inner_join(users::table.on(users::id.eq(dashboard_files::created_by))) + .filter(dashboard_files::deleted_at.is_null()) + .filter(dashboard_files::organization_id.eq_any(&user_org_ids)) + .filter(dashboard_files::workspace_sharing.ne(WorkspaceSharing::None)) + // Exclude dashboards we already have direct access to + .filter( + diesel::dsl::not(diesel::dsl::exists( + asset_permissions::table + .filter(asset_permissions::asset_id.eq(dashboard_files::id)) + .filter(asset_permissions::asset_type.eq(AssetType::DashboardFile)) + .filter(asset_permissions::deleted_at.is_null()) + .filter( + asset_permissions::identity_id + .eq(user.id) + .or( + asset_permissions::identity_type.eq(IdentityType::Team) + .and(diesel::dsl::exists( + teams_to_users::table + .filter(teams_to_users::team_id.eq(asset_permissions::identity_id)) + .filter(teams_to_users::user_id.eq(user.id)) + .filter(teams_to_users::deleted_at.is_null()) + )) + ) + ) + )) + ) + .select(( + dashboard_files::id, + dashboard_files::name, + dashboard_files::created_by, + dashboard_files::created_at, + dashboard_files::updated_at, + dashboard_files::workspace_sharing, + users::name.nullable(), + users::avatar_url.nullable(), + dashboard_files::organization_id, + )) + .order(( + dashboard_files::updated_at.desc(), + dashboard_files::id.asc(), + )) + .load::<( + Uuid, + String, + Uuid, + DateTime, + DateTime, + WorkspaceSharing, + Option, + Option, + Uuid, + )>(&mut conn) + .await + .map_err(|e| anyhow!("Error getting workspace shared dashboards: {}", e))?; + + for (id, name, created_by, created_at, updated_at, workspace_sharing, creator_name, creator_avatar_url, org_id) in workspace_shared_dashboards { + // Determine the effective permission based on workspace sharing + let role = match workspace_sharing { + WorkspaceSharing::CanView => AssetPermissionRole::CanView, + WorkspaceSharing::CanEdit => AssetPermissionRole::CanEdit, + WorkspaceSharing::FullAccess => AssetPermissionRole::FullAccess, + WorkspaceSharing::None => continue, // Should not happen due to filter + }; + + let owner = DashboardMember { + id: created_by, + name: creator_name.unwrap_or_else(|| "Unknown".to_string()), + avatar_url: creator_avatar_url, + }; + + let dashboard_item = BusterDashboardListItem { + id, + name, + created_at, + last_edited: updated_at, + owner, + members: vec![], + status: Verification::Verified, + is_shared: true, // Always shared for workspace-shared dashboards + }; + + dashboards.push(dashboard_item); + } + } + + // Sort dashboards by updated_at desc + dashboards.sort_by(|a, b| b.last_edited.cmp(&a.last_edited)); + + // Apply pagination after combining results + let start = request.page_token as usize * request.page_size as usize; + let end = start + request.page_size as usize; + let paginated_dashboards = dashboards.into_iter() + .skip(start) + .take(request.page_size as usize) + .collect(); + + Ok(paginated_dashboards) } diff --git a/apps/api/libs/handlers/src/dashboards/sharing/create_sharing_handler.rs b/apps/api/libs/handlers/src/dashboards/sharing/create_sharing_handler.rs index 50c2e4a8f..1bf496a2f 100644 --- a/apps/api/libs/handlers/src/dashboards/sharing/create_sharing_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/sharing/create_sharing_handler.rs @@ -53,6 +53,7 @@ pub async fn create_dashboard_sharing_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/sharing/delete_sharing_handler.rs b/apps/api/libs/handlers/src/dashboards/sharing/delete_sharing_handler.rs index dfb8f6ee7..9ced19d39 100644 --- a/apps/api/libs/handlers/src/dashboards/sharing/delete_sharing_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/sharing/delete_sharing_handler.rs @@ -53,6 +53,7 @@ pub async fn delete_dashboard_sharing_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/sharing/list_sharing_handler.rs b/apps/api/libs/handlers/src/dashboards/sharing/list_sharing_handler.rs index 9b64f350f..3b5942f6f 100644 --- a/apps/api/libs/handlers/src/dashboards/sharing/list_sharing_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/sharing/list_sharing_handler.rs @@ -53,6 +53,7 @@ pub async fn list_dashboard_sharing_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/sharing/update_sharing_handler.rs b/apps/api/libs/handlers/src/dashboards/sharing/update_sharing_handler.rs index 1a336aa0b..1447dda9e 100644 --- a/apps/api/libs/handlers/src/dashboards/sharing/update_sharing_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/sharing/update_sharing_handler.rs @@ -81,6 +81,7 @@ pub async fn update_dashboard_sharing_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/dashboards/update_dashboard_handler.rs b/apps/api/libs/handlers/src/dashboards/update_dashboard_handler.rs index 9f3505e85..392ba343f 100644 --- a/apps/api/libs/handlers/src/dashboards/update_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/update_dashboard_handler.rs @@ -86,6 +86,7 @@ pub async fn update_dashboard_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, + dashboard_with_permission.dashboard_file.workspace_sharing, ); if !has_permission { diff --git a/apps/api/libs/handlers/src/messages/helpers/delete_message_handler.rs b/apps/api/libs/handlers/src/messages/helpers/delete_message_handler.rs index 5579280fd..554edcf28 100644 --- a/apps/api/libs/handlers/src/messages/helpers/delete_message_handler.rs +++ b/apps/api/libs/handlers/src/messages/helpers/delete_message_handler.rs @@ -56,6 +56,7 @@ pub async fn delete_message_handler(user: AuthenticatedUser, message_id: Uuid) - ], chat_with_permission.chat.organization_id, &user.organizations, + chat_with_permission.chat.workspace_sharing, ); // If user is the creator, they automatically have access diff --git a/apps/api/libs/handlers/src/metrics/bulk_update_metrics_handler.rs b/apps/api/libs/handlers/src/metrics/bulk_update_metrics_handler.rs index 26ec80296..311d0cf2b 100644 --- a/apps/api/libs/handlers/src/metrics/bulk_update_metrics_handler.rs +++ b/apps/api/libs/handlers/src/metrics/bulk_update_metrics_handler.rs @@ -3,7 +3,7 @@ use database::helpers::metric_files::fetch_metric_files_with_permissions; use futures::future::join_all; use middleware::AuthenticatedUser; use sharing::check_permission_access; -use database::enums::AssetPermissionRole; +use database::enums::{AssetPermissionRole, WorkspaceSharing}; use std::collections::HashMap; use uuid::Uuid; @@ -143,6 +143,7 @@ pub async fn bulk_update_metrics_handler( ], *organization_id, &user.organizations, + WorkspaceSharing::None, // Use None as default for bulk handlers ) { // User has permission, process the update futures.push(process_single_update(update, user)); diff --git a/apps/api/libs/handlers/src/metrics/delete_metric_handler.rs b/apps/api/libs/handlers/src/metrics/delete_metric_handler.rs index fa7a68581..21bcac86b 100644 --- a/apps/api/libs/handlers/src/metrics/delete_metric_handler.rs +++ b/apps/api/libs/handlers/src/metrics/delete_metric_handler.rs @@ -57,6 +57,7 @@ pub async fn delete_metric_handler(metric_id: &Uuid, user: &AuthenticatedUser) - &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], metric_file.metric_file.organization_id, &user.organizations, + metric_file.metric_file.workspace_sharing, ) { return Err(anyhow!("You don't have permission to delete this metric")); } @@ -115,6 +116,7 @@ pub async fn delete_metrics_handler( &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], metric_with_permission.metric_file.organization_id, &user.organizations, + metric_with_permission.metric_file.workspace_sharing, ) { // Set the deleted_at timestamp for this metric match diesel::update(metric_files::table) diff --git a/apps/api/libs/handlers/src/metrics/get_metric_data_handler.rs b/apps/api/libs/handlers/src/metrics/get_metric_data_handler.rs index f7bb0ecdc..c5789494e 100644 --- a/apps/api/libs/handlers/src/metrics/get_metric_data_handler.rs +++ b/apps/api/libs/handlers/src/metrics/get_metric_data_handler.rs @@ -9,6 +9,7 @@ use diesel_async::RunQueryDsl; use indexmap::IndexMap; use middleware::AuthenticatedUser; use serde::{Deserialize, Serialize}; +use sharing::asset_access_checks::check_metric_collection_access; use uuid::Uuid; use query_engine::data_types::DataType; @@ -72,7 +73,7 @@ pub async fn get_metric_data_handler( ); // Check if user has access to ANY dashboard containing this metric (including public dashboards) - let has_dashboard_access = sharing::check_metric_dashboard_access(&request.metric_id, &user.id) + let has_dashboard_access = sharing::check_metric_dashboard_access(&request.metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -102,7 +103,7 @@ pub async fn get_metric_data_handler( } else { // No dashboard access, check if user has access via a chat tracing::info!("No dashboard association found. Checking chat access."); - let has_chat_access = sharing::check_metric_chat_access(&request.metric_id, &user.id) + let has_chat_access = sharing::check_metric_chat_access(&request.metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -130,9 +131,40 @@ pub async fn get_metric_data_handler( } } } else { - // No dashboard or chat access, return the original permission error - tracing::warn!("No dashboard or chat association found for metric. Returning original error."); - return Err(e); + // No chat access, check if user has access via a collection + tracing::info!("No chat association found. Checking collection access."); + let has_collection_access = check_metric_collection_access(&request.metric_id, &user.id, &user.organizations) + .await + .unwrap_or(false); + + if has_collection_access { + // User has access to a collection containing this metric + tracing::info!("Found associated collection with user access. Fetching metric with collection context."); + match get_metric_for_dashboard_handler( + &request.metric_id, + &user, + request.version_number, + request.password.clone(), + ) + .await + { + Ok(metric_via_collection) => { + tracing::debug!( + "Successfully retrieved metric via collection association." + ); + metric_via_collection // Use this metric definition + } + Err(fetch_err) => { + // If fetching via collection fails unexpectedly, return that error + tracing::error!("Failed to fetch metric via collection context: {}", fetch_err); + return Err(fetch_err); + } + } + } else { + // No dashboard, chat, or collection access, return the original permission error + tracing::warn!("No dashboard, chat, or collection association found for metric. Returning original error."); + return Err(e); + } } } } else { diff --git a/apps/api/libs/handlers/src/metrics/get_metric_for_dashboard_handler.rs b/apps/api/libs/handlers/src/metrics/get_metric_for_dashboard_handler.rs index abe33cb54..44814021f 100644 --- a/apps/api/libs/handlers/src/metrics/get_metric_for_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/metrics/get_metric_for_dashboard_handler.rs @@ -6,6 +6,7 @@ use diesel_async::{AsyncPgConnection, RunQueryDsl}; use futures::future::join; use middleware::AuthenticatedUser; use serde_yaml; +use sharing::asset_access_checks::check_metric_collection_access; use uuid::Uuid; use crate::metrics::types::{AssociatedCollection, AssociatedDashboard, BusterMetric, BusterShareIndividual, Dataset}; @@ -136,6 +137,7 @@ pub async fn get_metric_for_dashboard_handler( ], metric_file.organization_id, &user.organizations, + metric_file.workspace_sharing, ); tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); @@ -160,7 +162,7 @@ pub async fn get_metric_for_dashboard_handler( // No sufficient direct/admin permission, check if user has access via a dashboard tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access."); - let has_dashboard_access = sharing::check_metric_dashboard_access(metric_id, &user.id) + let has_dashboard_access = sharing::check_metric_dashboard_access(metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -172,7 +174,7 @@ pub async fn get_metric_for_dashboard_handler( // No dashboard access, check if user has access via a chat tracing::debug!(metric_id = %metric_id, "No dashboard access. Checking chat access."); - let has_chat_access = sharing::check_metric_chat_access(metric_id, &user.id) + let has_chat_access = sharing::check_metric_chat_access(metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -181,16 +183,28 @@ pub async fn get_metric_for_dashboard_handler( tracing::debug!(metric_id = %metric_id, user_id = %user.id, "User has access via chat. Granting CanView."); permission = AssetPermissionRole::CanView; } else { - // No chat access either, check public access rules - tracing::debug!(metric_id = %metric_id, "No chat access. Checking public access rules."); - if !metric_file.publicly_accessible { - tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Permission denied (not public, no dashboard/chat access, insufficient direct permission)."); - return Err(anyhow!("You don't have permission to view this metric")); - } - tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible."); + // No chat access, check if user has access via a collection + tracing::debug!(metric_id = %metric_id, "No chat access. Checking collection access."); + + let has_collection_access = check_metric_collection_access(metric_id, &user.id, &user.organizations) + .await + .unwrap_or(false); + + if has_collection_access { + // User has access to a collection containing this metric, grant CanView + tracing::debug!(metric_id = %metric_id, user_id = %user.id, "User has access via collection. Granting CanView."); + permission = AssetPermissionRole::CanView; + } else { + // No collection access either, check public access rules + tracing::debug!(metric_id = %metric_id, "No collection access. Checking public access rules."); + if !metric_file.publicly_accessible { + tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Permission denied (not public, no dashboard/chat/collection access, insufficient direct permission)."); + return Err(anyhow!("You don't have permission to view this metric")); + } + tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible."); - // Check if the public access has expired - if let Some(expiry_date) = metric_file.public_expiry_date { + // Check if the public access has expired + if let Some(expiry_date) = metric_file.public_expiry_date { tracing::debug!(metric_id = %metric_id, ?expiry_date, "Checking expiry date"); if expiry_date < chrono::Utc::now() { tracing::warn!(metric_id = %metric_id, "Public access expired"); @@ -219,10 +233,11 @@ pub async fn get_metric_for_dashboard_handler( return Err(anyhow!("public_password required for this metric")); } } - } else { - // Publicly accessible, not expired, and no password required - tracing::debug!(metric_id = %metric_id, "Public access granted (no password required)."); - permission = AssetPermissionRole::CanView; + } else { + // Publicly accessible, not expired, and no password required + tracing::debug!(metric_id = %metric_id, "Public access granted (no password required)."); + permission = AssetPermissionRole::CanView; + } } } } diff --git a/apps/api/libs/handlers/src/metrics/get_metric_handler.rs b/apps/api/libs/handlers/src/metrics/get_metric_handler.rs index 41c7ca42c..2c5c4cd1c 100644 --- a/apps/api/libs/handlers/src/metrics/get_metric_handler.rs +++ b/apps/api/libs/handlers/src/metrics/get_metric_handler.rs @@ -4,6 +4,7 @@ use diesel_async::RunQueryDsl; use futures::future::join; use middleware::AuthenticatedUser; use serde_yaml; +use sharing::asset_access_checks::check_metric_collection_access; use uuid::Uuid; use crate::metrics::types::{AssociatedCollection, AssociatedDashboard, BusterMetric, Dataset}; @@ -134,6 +135,7 @@ pub async fn get_metric_handler( ], metric_file.organization_id, &user.organizations, + metric_file.workspace_sharing, ); tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); @@ -158,7 +160,7 @@ pub async fn get_metric_handler( // No sufficient direct/admin permission, check if user has access via a dashboard tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access."); - let has_dashboard_access = sharing::check_metric_dashboard_access(metric_id, &user.id) + let has_dashboard_access = sharing::check_metric_dashboard_access(metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -170,7 +172,7 @@ pub async fn get_metric_handler( // No dashboard access, check if user has access via a chat tracing::debug!(metric_id = %metric_id, "No dashboard access. Checking chat access."); - let has_chat_access = sharing::check_metric_chat_access(metric_id, &user.id) + let has_chat_access = sharing::check_metric_chat_access(metric_id, &user.id, &user.organizations) .await .unwrap_or(false); @@ -179,16 +181,28 @@ pub async fn get_metric_handler( tracing::debug!(metric_id = %metric_id, user_id = %user.id, "User has access via chat. Granting CanView."); permission = AssetPermissionRole::CanView; } else { - // No chat access either, check public access rules - tracing::debug!(metric_id = %metric_id, "No chat access. Checking public access rules."); - if !metric_file.publicly_accessible { - tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Permission denied (not public, no dashboard/chat access, insufficient direct permission)."); - return Err(anyhow!("You don't have permission to view this metric")); - } - tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible."); + // No chat access, check if user has access via a collection + tracing::debug!(metric_id = %metric_id, "No chat access. Checking collection access."); + + let has_collection_access = check_metric_collection_access(metric_id, &user.id, &user.organizations) + .await + .unwrap_or(false); + + if has_collection_access { + // User has access to a collection containing this metric, grant CanView + tracing::debug!(metric_id = %metric_id, user_id = %user.id, "User has access via collection. Granting CanView."); + permission = AssetPermissionRole::CanView; + } else { + // No collection access either, check public access rules + tracing::debug!(metric_id = %metric_id, "No collection access. Checking public access rules."); + if !metric_file.publicly_accessible { + tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Permission denied (not public, no dashboard/chat/collection access, insufficient direct permission)."); + return Err(anyhow!("You don't have permission to view this metric")); + } + tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible."); - // Check if the public access has expired - if let Some(expiry_date) = metric_file.public_expiry_date { + // Check if the public access has expired + if let Some(expiry_date) = metric_file.public_expiry_date { tracing::debug!(metric_id = %metric_id, ?expiry_date, "Checking expiry date"); if expiry_date < chrono::Utc::now() { tracing::warn!(metric_id = %metric_id, "Public access expired"); @@ -217,10 +231,11 @@ pub async fn get_metric_handler( return Err(anyhow!("public_password required for this metric")); } } - } else { - // Publicly accessible, not expired, and no password required - tracing::debug!(metric_id = %metric_id, "Public access granted (no password required)."); - permission = AssetPermissionRole::CanView; + } else { + // Publicly accessible, not expired, and no password required + tracing::debug!(metric_id = %metric_id, "Public access granted (no password required)."); + permission = AssetPermissionRole::CanView; + } } } } diff --git a/apps/api/libs/handlers/src/metrics/list_metrics_handler.rs b/apps/api/libs/handlers/src/metrics/list_metrics_handler.rs index d28cb1d28..7e5a96455 100644 --- a/apps/api/libs/handlers/src/metrics/list_metrics_handler.rs +++ b/apps/api/libs/handlers/src/metrics/list_metrics_handler.rs @@ -1,9 +1,9 @@ use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetPermissionRole, AssetType, IdentityType, Verification}, + enums::{AssetPermissionRole, AssetType, IdentityType, Verification, WorkspaceSharing}, pool::get_pg_pool, - schema::{asset_permissions, metric_files, users}, + schema::{asset_permissions, metric_files, users, teams_to_users}, }; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; @@ -80,7 +80,7 @@ pub async fn list_metrics_handler( .into_boxed(); // Add filters based on request parameters - if let Some(verification_statuses) = request.verification { + if let Some(verification_statuses) = request.verification.clone() { // Only apply filter if the vec is not empty if !verification_statuses.is_empty() { metric_statement = metric_statement.filter(metric_files::verification.eq_any(verification_statuses)); @@ -126,8 +126,82 @@ pub async fn list_metrics_handler( Err(e) => return Err(anyhow!("Error getting metric results: {}", e)), }; + // Get user's organization IDs + let user_org_ids: Vec = user.organizations.iter().map(|org| org.id).collect(); + + // Second query: Get workspace-shared metrics that the user doesn't have direct access to + let mut workspace_shared_statement = metric_files::table + .inner_join(users::table.on(metric_files::created_by.eq(users::id))) + .filter(metric_files::deleted_at.is_null()) + .filter(metric_files::organization_id.eq_any(&user_org_ids)) + .filter(metric_files::workspace_sharing.ne(WorkspaceSharing::None)) + // Exclude metrics we already have direct access to + .filter( + diesel::dsl::not(diesel::dsl::exists( + asset_permissions::table + .filter(asset_permissions::asset_id.eq(metric_files::id)) + .filter(asset_permissions::asset_type.eq(AssetType::MetricFile)) + .filter(asset_permissions::deleted_at.is_null()) + .filter( + asset_permissions::identity_id + .eq(user.id) + .or(teams_to_users::table + .filter(teams_to_users::team_id.eq(asset_permissions::identity_id)) + .filter(teams_to_users::user_id.eq(user.id)) + .filter(teams_to_users::deleted_at.is_null()) + .select(teams_to_users::user_id) + .single_value() + .is_not_null()), + ), + )), + ) + .select(( + metric_files::id, + metric_files::name, + metric_files::created_by, + metric_files::created_at, + metric_files::updated_at, + metric_files::verification, + metric_files::workspace_sharing, + users::name.nullable(), + users::email, + users::avatar_url.nullable(), + )) + .order((metric_files::updated_at.desc(), metric_files::id.asc())) + .offset(offset) + .limit(request.page_size) + .into_boxed(); + + // Apply filters to workspace-shared query + if let Some(verification_statuses) = &request.verification { + if !verification_statuses.is_empty() { + workspace_shared_statement = workspace_shared_statement.filter(metric_files::verification.eq_any(verification_statuses)); + } + } + + // Don't include workspace-shared metrics for only_my_metrics filter + let workspace_shared_results = if request.only_my_metrics == Some(true) { + vec![] + } else { + workspace_shared_statement + .load::<( + Uuid, + String, + Uuid, + DateTime, + DateTime, + Verification, + WorkspaceSharing, + Option, + String, + Option, + )>(&mut conn) + .await + .map_err(|e| anyhow!("Error getting workspace-shared metrics: {}", e))? + }; + // Transform query results into BusterMetricListItem - let metrics = metric_results + let mut metrics: Vec = metric_results .into_iter() .map( |( @@ -149,5 +223,20 @@ pub async fn list_metrics_handler( ) .collect(); + // Add workspace-shared metrics + for (id, name, created_by, _created_at, updated_at, status, _workspace_sharing, created_by_name, created_by_email, created_by_avatar) in workspace_shared_results { + metrics.push(BusterMetricListItem { + id, + name, + last_edited: updated_at, + created_by_id: created_by, + created_by_name: created_by_name.unwrap_or(created_by_email.clone()), + created_by_email, + created_by_avatar, + status, + is_shared: true, // Always true for workspace-shared metrics + }); + } + Ok(metrics) } diff --git a/apps/api/libs/handlers/src/metrics/sharing/create_sharing_handler.rs b/apps/api/libs/handlers/src/metrics/sharing/create_sharing_handler.rs index 426aa2697..a792e17c7 100644 --- a/apps/api/libs/handlers/src/metrics/sharing/create_sharing_handler.rs +++ b/apps/api/libs/handlers/src/metrics/sharing/create_sharing_handler.rs @@ -47,6 +47,7 @@ pub async fn create_metric_sharing_handler( &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], metric_file.metric_file.organization_id, &user.organizations, + metric_file.metric_file.workspace_sharing, ) { return Err(anyhow!("You don't have permission to share this metric")); } diff --git a/apps/api/libs/handlers/src/metrics/sharing/delete_sharing_handler.rs b/apps/api/libs/handlers/src/metrics/sharing/delete_sharing_handler.rs index 2a159b5a0..03e95cce9 100644 --- a/apps/api/libs/handlers/src/metrics/sharing/delete_sharing_handler.rs +++ b/apps/api/libs/handlers/src/metrics/sharing/delete_sharing_handler.rs @@ -47,6 +47,7 @@ pub async fn delete_metric_sharing_handler( &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], metric_file.metric_file.organization_id, &user.organizations, + metric_file.metric_file.workspace_sharing, ) { return Err(anyhow!( "You don't have permission to delete sharing for this metric" diff --git a/apps/api/libs/handlers/src/metrics/sharing/update_sharing_handler.rs b/apps/api/libs/handlers/src/metrics/sharing/update_sharing_handler.rs index 5945a269c..7760a8086 100644 --- a/apps/api/libs/handlers/src/metrics/sharing/update_sharing_handler.rs +++ b/apps/api/libs/handlers/src/metrics/sharing/update_sharing_handler.rs @@ -78,6 +78,7 @@ pub async fn update_metric_sharing_handler( &[AssetPermissionRole::FullAccess, AssetPermissionRole::Owner], metric_file.metric_file.organization_id, &user.organizations, + metric_file.metric_file.workspace_sharing, ) { return Err(anyhow!( "You don't have permission to update sharing for this metric" diff --git a/apps/api/libs/handlers/src/metrics/update_metric_handler.rs b/apps/api/libs/handlers/src/metrics/update_metric_handler.rs index 966e64e76..998794d6c 100644 --- a/apps/api/libs/handlers/src/metrics/update_metric_handler.rs +++ b/apps/api/libs/handlers/src/metrics/update_metric_handler.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, bail, Result}; use chrono::{DateTime, Utc}; use database::{ - enums::{AssetPermissionRole, AssetType, DataSourceType, IdentityType, Verification}, + enums::{AssetPermissionRole, AssetType, DataSourceType, IdentityType, Verification, WorkspaceSharing}, helpers::metric_files::fetch_metric_file_with_permissions, models::{Dataset, MetricFile, MetricFileToDataset}, pool::get_pg_pool, @@ -131,6 +131,7 @@ pub async fn update_metric_handler( ], organization_id, &user.organizations, + WorkspaceSharing::None, // Use None as default for update handlers ) { return Err(anyhow!( "You don't have permission to update this metric. Editor or higher role required." diff --git a/apps/api/libs/sharing/src/asset_access_checks.rs b/apps/api/libs/sharing/src/asset_access_checks.rs index 2d34ab0e2..8fd4e5f66 100644 --- a/apps/api/libs/sharing/src/asset_access_checks.rs +++ b/apps/api/libs/sharing/src/asset_access_checks.rs @@ -1,6 +1,6 @@ -use database::enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole}; +use database::enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole, WorkspaceSharing}; use database::pool::get_pg_pool; -use database::schema::{asset_permissions, dashboard_files, metric_files_to_dashboard_files}; +use database::schema::{asset_permissions, dashboard_files, metric_files_to_dashboard_files, collections, collections_to_assets}; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, OptionalExtension}; use diesel_async::RunQueryDsl; use middleware::OrganizationMembership; @@ -13,6 +13,7 @@ use uuid::Uuid; /// * `required_permission_level` - Required permission level to access the asset /// * `organization_id` - UUID of the organization /// * `organization_role_grants` - Array of tuples containing (UUID, UserOrganizationRole) for the user +/// * `workspace_sharing` - Workspace sharing level for the asset /// /// # Returns /// * `bool` - True if the user has sufficient permissions, false otherwise @@ -21,6 +22,7 @@ pub fn check_permission_access( required_permission_level: &[AssetPermissionRole], organization_id: Uuid, organization_role_grants: &[OrganizationMembership], + workspace_sharing: WorkspaceSharing, ) -> bool { // First check if the user has WorkspaceAdmin or DataAdmin role for the organization for org in organization_role_grants { @@ -32,6 +34,25 @@ pub fn check_permission_access( } } + // Check if user is member of the workspace and asset is shared + if workspace_sharing != WorkspaceSharing::None { + for org in organization_role_grants { + if org.id == organization_id { + // Map workspace sharing level to permission role + let workspace_permission = match workspace_sharing { + WorkspaceSharing::CanView => AssetPermissionRole::CanView, + WorkspaceSharing::CanEdit => AssetPermissionRole::CanEdit, + WorkspaceSharing::FullAccess => AssetPermissionRole::FullAccess, + WorkspaceSharing::None => unreachable!(), + }; + + if required_permission_level.contains(&workspace_permission) { + return true; + } + } + } + } + // Then check if the user has the required permission level if let Some(permission) = current_permission_level { if required_permission_level.contains(&permission) { @@ -46,18 +67,20 @@ pub fn check_permission_access( /// Checks if a user has access to a metric through any associated dashboard. /// /// This function is used to implement permission cascading from dashboards to metrics. -/// If a user has access to any dashboard containing the metric (either through direct permissions -/// or if the dashboard is public), they get at least CanView permission. +/// If a user has access to any dashboard containing the metric (either through direct permissions, +/// workspace sharing, or if the dashboard is public), they get at least CanView permission. /// /// # Arguments /// * `metric_id` - UUID of the metric to check /// * `user_id` - UUID of the user to check permissions for +/// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any dashboard containing the metric, false otherwise pub async fn check_metric_dashboard_access( metric_id: &Uuid, user_id: &Uuid, + user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( @@ -114,23 +137,52 @@ pub async fn check_metric_dashboard_access( .await .optional()?; - Ok(has_public_access.is_some()) + if has_public_access.is_some() { + return Ok(true); + } + + // Check if metric belongs to any workspace-shared dashboard + let workspace_shared_dashboard = metric_files_to_dashboard_files::table + .inner_join( + dashboard_files::table + .on(dashboard_files::id.eq(metric_files_to_dashboard_files::dashboard_file_id)), + ) + .filter(metric_files_to_dashboard_files::metric_file_id.eq(metric_id)) + .filter(dashboard_files::deleted_at.is_null()) + .filter(metric_files_to_dashboard_files::deleted_at.is_null()) + .filter(dashboard_files::workspace_sharing.ne(WorkspaceSharing::None)) + .select((dashboard_files::organization_id, dashboard_files::workspace_sharing)) + .first::<(Uuid, WorkspaceSharing)>(&mut conn) + .await + .optional()?; + + if let Some((org_id, _sharing_level)) = workspace_shared_dashboard { + // Check if user is member of that organization + if user_orgs.iter().any(|org| org.id == org_id) { + return Ok(true); + } + } + + Ok(false) } /// Checks if a user has access to a metric through any associated chat. /// /// This function is used to implement permission cascading from chats to metrics. -/// If a user has access to any chat containing the metric, they get at least CanView permission. +/// If a user has access to any chat containing the metric (either through direct permissions, +/// workspace sharing, or if the chat is public), they get at least CanView permission. /// /// # Arguments /// * `metric_id` - UUID of the metric to check /// * `user_id` - UUID of the user to check permissions for +/// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any chat containing the metric, false otherwise pub async fn check_metric_chat_access( metric_id: &Uuid, user_id: &Uuid, + user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( @@ -197,23 +249,57 @@ pub async fn check_metric_chat_access( .await .optional()?; - Ok(has_public_chat_access.is_some()) + if has_public_chat_access.is_some() { + return Ok(true); + } + + // Check if metric belongs to any workspace-shared chat + let workspace_shared_chat = database::schema::messages_to_files::table + .inner_join( + database::schema::messages::table + .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), + ) + .inner_join( + database::schema::chats::table + .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), + ) + .filter(database::schema::messages_to_files::file_id.eq(metric_id)) + .filter(database::schema::messages_to_files::deleted_at.is_null()) + .filter(database::schema::messages::deleted_at.is_null()) + .filter(database::schema::chats::deleted_at.is_null()) + .filter(database::schema::chats::workspace_sharing.ne(WorkspaceSharing::None)) + .select((database::schema::chats::organization_id, database::schema::chats::workspace_sharing)) + .first::<(Uuid, WorkspaceSharing)>(&mut conn) + .await + .optional()?; + + if let Some((org_id, _sharing_level)) = workspace_shared_chat { + // Check if user is member of that organization + if user_orgs.iter().any(|org| org.id == org_id) { + return Ok(true); + } + } + + Ok(false) } /// Checks if a user has access to a dashboard through any associated chat. /// /// This function is used to implement permission cascading from chats to dashboards. -/// If a user has access to any chat containing the dashboard, they get at least CanView permission. +/// If a user has access to any chat containing the dashboard (either through direct permissions, +/// workspace sharing, or if the chat is public), they get at least CanView permission. /// /// # Arguments /// * `dashboard_id` - UUID of the dashboard to check /// * `user_id` - UUID of the user to check permissions for +/// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any chat containing the dashboard, false otherwise pub async fn check_dashboard_chat_access( dashboard_id: &Uuid, user_id: &Uuid, + user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( @@ -280,7 +366,200 @@ pub async fn check_dashboard_chat_access( .await .optional()?; - Ok(has_public_chat_access.is_some()) + if has_public_chat_access.is_some() { + return Ok(true); + } + + // Check if dashboard belongs to any workspace-shared chat + let workspace_shared_chat = database::schema::messages_to_files::table + .inner_join( + database::schema::messages::table + .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), + ) + .inner_join( + database::schema::chats::table + .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), + ) + .filter(database::schema::messages_to_files::file_id.eq(dashboard_id)) + .filter(database::schema::messages_to_files::deleted_at.is_null()) + .filter(database::schema::messages::deleted_at.is_null()) + .filter(database::schema::chats::deleted_at.is_null()) + .filter(database::schema::chats::workspace_sharing.ne(WorkspaceSharing::None)) + .select((database::schema::chats::organization_id, database::schema::chats::workspace_sharing)) + .first::<(Uuid, WorkspaceSharing)>(&mut conn) + .await + .optional()?; + + if let Some((org_id, _sharing_level)) = workspace_shared_chat { + // Check if user is member of that organization + if user_orgs.iter().any(|org| org.id == org_id) { + return Ok(true); + } + } + + Ok(false) +} + +/// Checks if a user has access to a metric through any associated collection. +/// +/// This function is used to implement permission cascading from collections to metrics. +/// If a user has access to any collection containing the metric (either through direct permissions, +/// workspace sharing, or if the collection is public), they get at least CanView permission. +/// +/// # Arguments +/// * `metric_id` - UUID of the metric to check +/// * `user_id` - UUID of the user to check permissions for +/// * `user_orgs` - User's organization memberships +/// +/// # Returns +/// * `Result` - True if the user has access to any collection containing the metric, false otherwise +pub async fn check_metric_collection_access( + metric_id: &Uuid, + user_id: &Uuid, + user_orgs: &[OrganizationMembership], +) -> Result { + let mut conn = get_pg_pool().get().await.map_err(|e| { + diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::UnableToSendCommand, + Box::new(e.to_string()), + ) + })?; + + // First check if user has direct access to any collection containing this metric + let has_direct_access = collections_to_assets::table + .inner_join( + collections::table + .on(collections::id.eq(collections_to_assets::collection_id)), + ) + .inner_join( + asset_permissions::table.on( + asset_permissions::asset_id.eq(collections::id) + .and(asset_permissions::asset_type.eq(AssetType::Collection)) + .and(asset_permissions::identity_id.eq(user_id)) + .and(asset_permissions::identity_type.eq(IdentityType::User)) + .and(asset_permissions::deleted_at.is_null()) + ), + ) + .filter(collections_to_assets::asset_id.eq(metric_id)) + .filter(collections_to_assets::asset_type.eq(AssetType::MetricFile)) + .filter(collections::deleted_at.is_null()) + .filter(collections_to_assets::deleted_at.is_null()) + .select(collections::id) + .first::(&mut conn) + .await + .optional()?; + + if has_direct_access.is_some() { + return Ok(true); + } + + // Note: Collections don't have publicly_accessible fields, only workspace_sharing + + // Check if metric belongs to any workspace-shared collection + let workspace_shared_collection = collections_to_assets::table + .inner_join( + collections::table + .on(collections::id.eq(collections_to_assets::collection_id)), + ) + .filter(collections_to_assets::asset_id.eq(metric_id)) + .filter(collections_to_assets::asset_type.eq(AssetType::MetricFile)) + .filter(collections::deleted_at.is_null()) + .filter(collections_to_assets::deleted_at.is_null()) + .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) + .select((collections::organization_id, collections::workspace_sharing)) + .first::<(Uuid, WorkspaceSharing)>(&mut conn) + .await + .optional()?; + + if let Some((org_id, _sharing_level)) = workspace_shared_collection { + // Check if user is member of that organization + if user_orgs.iter().any(|org| org.id == org_id) { + return Ok(true); + } + } + + Ok(false) +} + +/// Checks if a user has access to a dashboard through any associated collection. +/// +/// This function is used to implement permission cascading from collections to dashboards. +/// If a user has access to any collection containing the dashboard (either through direct permissions, +/// workspace sharing, or if the collection is public), they get at least CanView permission. +/// +/// # Arguments +/// * `dashboard_id` - UUID of the dashboard to check +/// * `user_id` - UUID of the user to check permissions for +/// * `user_orgs` - User's organization memberships +/// +/// # Returns +/// * `Result` - True if the user has access to any collection containing the dashboard, false otherwise +pub async fn check_dashboard_collection_access( + dashboard_id: &Uuid, + user_id: &Uuid, + user_orgs: &[OrganizationMembership], +) -> Result { + let mut conn = get_pg_pool().get().await.map_err(|e| { + diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::UnableToSendCommand, + Box::new(e.to_string()), + ) + })?; + + // First check if user has direct access to any collection containing this dashboard + let has_direct_access = collections_to_assets::table + .inner_join( + collections::table + .on(collections::id.eq(collections_to_assets::collection_id)), + ) + .inner_join( + asset_permissions::table.on( + asset_permissions::asset_id.eq(collections::id) + .and(asset_permissions::asset_type.eq(AssetType::Collection)) + .and(asset_permissions::identity_id.eq(user_id)) + .and(asset_permissions::identity_type.eq(IdentityType::User)) + .and(asset_permissions::deleted_at.is_null()) + ), + ) + .filter(collections_to_assets::asset_id.eq(dashboard_id)) + .filter(collections_to_assets::asset_type.eq(AssetType::DashboardFile)) + .filter(collections::deleted_at.is_null()) + .filter(collections_to_assets::deleted_at.is_null()) + .select(collections::id) + .first::(&mut conn) + .await + .optional()?; + + if has_direct_access.is_some() { + return Ok(true); + } + + // Note: Collections don't have publicly_accessible fields, only workspace_sharing + + // Check if dashboard belongs to any workspace-shared collection + let workspace_shared_collection = collections_to_assets::table + .inner_join( + collections::table + .on(collections::id.eq(collections_to_assets::collection_id)), + ) + .filter(collections_to_assets::asset_id.eq(dashboard_id)) + .filter(collections_to_assets::asset_type.eq(AssetType::DashboardFile)) + .filter(collections::deleted_at.is_null()) + .filter(collections_to_assets::deleted_at.is_null()) + .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) + .select((collections::organization_id, collections::workspace_sharing)) + .first::<(Uuid, WorkspaceSharing)>(&mut conn) + .await + .optional()?; + + if let Some((org_id, _sharing_level)) = workspace_shared_collection { + // Check if user is member of that organization + if user_orgs.iter().any(|org| org.id == org_id) { + return Ok(true); + } + } + + Ok(false) } #[cfg(test)] @@ -299,7 +578,8 @@ mod tests { None, &[AssetPermissionRole::Owner], org_id, - &grants + &grants, + WorkspaceSharing::None )); } @@ -315,7 +595,8 @@ mod tests { None, &[AssetPermissionRole::Owner], org_id, - &grants + &grants, + WorkspaceSharing::None )); } @@ -331,7 +612,8 @@ mod tests { Some(AssetPermissionRole::CanEdit), &[AssetPermissionRole::CanEdit], org_id, - &grants + &grants, + WorkspaceSharing::None )); } @@ -347,7 +629,8 @@ mod tests { Some(AssetPermissionRole::CanView), &[AssetPermissionRole::CanEdit], org_id, - &grants + &grants, + WorkspaceSharing::None )); } @@ -363,7 +646,8 @@ mod tests { None, &[AssetPermissionRole::CanView], org_id, - &grants + &grants, + WorkspaceSharing::None )); } } diff --git a/apps/api/server/src/routes/rest/routes/chats/duplicate_chat.rs b/apps/api/server/src/routes/rest/routes/chats/duplicate_chat.rs index 6b6466622..903249963 100644 --- a/apps/api/server/src/routes/rest/routes/chats/duplicate_chat.rs +++ b/apps/api/server/src/routes/rest/routes/chats/duplicate_chat.rs @@ -60,6 +60,7 @@ mod tests { use axum::routing::post; use axum::Router; use chrono::Utc; + use database::enums::WorkspaceSharing; use database::{ enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole}, models::{AssetPermission, Chat, Message, MessageToFile, User}, @@ -137,6 +138,9 @@ mod tests { most_recent_file_id: None, most_recent_file_type: None, most_recent_version_number: None, + workspace_sharing: WorkspaceSharing::None, + workspace_sharing_enabled_at: None, + workspace_sharing_enabled_by: None, }; let mut conn = get_pg_pool().get().await.unwrap(); @@ -184,6 +188,8 @@ mod tests { deleted_at: None, created_by: user.id, feedback: None, + is_completed: true, + post_processing_message: None, }; insert_into(messages::table) diff --git a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts index c4db01d46..c3ae13618 100644 --- a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts +++ b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts @@ -5,6 +5,7 @@ import { getReactions, getThreadMessages, removeReaction, + convertMarkdownToSlack, } from '@buster/slack'; import { type TaskOutput, logger, runs, schemaTask, wait } from '@trigger.dev/sdk'; import { z } from 'zod'; @@ -546,32 +547,43 @@ export const slackAgentTask: ReturnType< buttonUrl = `${busterUrl}/app/chats/${payload.chatId}/${chatFileInfo.mostRecentFileType}s/${chatFileInfo.mostRecentFileId}?${chatFileInfo.mostRecentFileType}_version_number=${chatFileInfo.mostRecentVersionNumber}`; } - const completionMessage = { - text: responseText, - thread_ts: chatDetails.slackThreadTs, - blocks: [ - { - type: 'section' as const, - text: { - type: 'mrkdwn' as const, - text: responseText, - }, + // Convert markdown to Slack format + const convertedResponse = convertMarkdownToSlack(responseText); + + // Create the message with converted text and any blocks from conversion + const messageBlocks = [...(convertedResponse.blocks || [])]; + + // If no blocks were created from conversion, create a section block with the converted text + if (messageBlocks.length === 0 && convertedResponse.text) { + messageBlocks.push({ + type: 'section' as const, + text: { + type: 'mrkdwn' as const, + text: convertedResponse.text, }, + }); + } + + // Add the action button block + messageBlocks.push({ + type: 'actions' as const, + elements: [ { - type: 'actions' as const, - elements: [ - { - type: 'button' as const, - text: { - type: 'plain_text' as const, - text: 'Open in Buster', - emoji: false, - }, - url: buttonUrl, - }, - ], + type: 'button' as const, + text: { + type: 'plain_text' as const, + text: 'Open in Buster', + emoji: false, + }, + url: buttonUrl, }, ], + }); + + const completionMessage = { + text: convertedResponse.text || responseText, // Use converted text as fallback + thread_ts: chatDetails.slackThreadTs, + blocks: messageBlocks, }; await messagingService.sendMessage( diff --git a/packages/database/drizzle/0081_curly_silvermane.sql b/packages/database/drizzle/0081_curly_silvermane.sql new file mode 100644 index 000000000..8ce1d59b4 --- /dev/null +++ b/packages/database/drizzle/0081_curly_silvermane.sql @@ -0,0 +1,17 @@ +CREATE TYPE "public"."workspace_sharing_enum" AS ENUM('none', 'can_view', 'can_edit', 'full_access');--> statement-breakpoint +ALTER TABLE "chats" ADD COLUMN "workspace_sharing" "workspace_sharing_enum" DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "chats" ADD COLUMN "workspace_sharing_enabled_by" uuid;--> statement-breakpoint +ALTER TABLE "chats" ADD COLUMN "workspace_sharing_enabled_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "collections" ADD COLUMN "workspace_sharing" "workspace_sharing_enum" DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "collections" ADD COLUMN "workspace_sharing_enabled_by" uuid;--> statement-breakpoint +ALTER TABLE "collections" ADD COLUMN "workspace_sharing_enabled_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "dashboard_files" ADD COLUMN "workspace_sharing" "workspace_sharing_enum" DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "dashboard_files" ADD COLUMN "workspace_sharing_enabled_by" uuid;--> statement-breakpoint +ALTER TABLE "dashboard_files" ADD COLUMN "workspace_sharing_enabled_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "metric_files" ADD COLUMN "workspace_sharing" "workspace_sharing_enum" DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "metric_files" ADD COLUMN "workspace_sharing_enabled_by" uuid;--> statement-breakpoint +ALTER TABLE "metric_files" ADD COLUMN "workspace_sharing_enabled_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "chats" ADD CONSTRAINT "chats_workspace_sharing_enabled_by_fkey" FOREIGN KEY ("workspace_sharing_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "collections" ADD CONSTRAINT "collections_workspace_sharing_enabled_by_fkey" FOREIGN KEY ("workspace_sharing_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "dashboard_files" ADD CONSTRAINT "dashboard_files_workspace_sharing_enabled_by_fkey" FOREIGN KEY ("workspace_sharing_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "metric_files" ADD CONSTRAINT "metric_files_workspace_sharing_enabled_by_fkey" FOREIGN KEY ("workspace_sharing_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade; \ No newline at end of file diff --git a/packages/database/drizzle/meta/0081_snapshot.json b/packages/database/drizzle/meta/0081_snapshot.json new file mode 100644 index 000000000..3ed14d6ba --- /dev/null +++ b/packages/database/drizzle/meta/0081_snapshot.json @@ -0,0 +1,6383 @@ +{ + "id": "2969a455-c2ba-4650-8f69-2719e3458967", + "prevId": "0225764d-f5a8-42d4-965d-a3bcd03225ec", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_organization_id_fkey": { + "name": "api_keys_organization_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_keys_owner_id_fkey": { + "name": "api_keys_owner_id_fkey", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_key_key": { + "name": "api_keys_key_key", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset_permissions": { + "name": "asset_permissions", + "schema": "", + "columns": { + "identity_id": { + "name": "identity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_type": { + "name": "identity_type", + "type": "identity_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "asset_permission_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "asset_permissions_created_by_fkey": { + "name": "asset_permissions_created_by_fkey", + "tableFrom": "asset_permissions", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "asset_permissions_updated_by_fkey": { + "name": "asset_permissions_updated_by_fkey", + "tableFrom": "asset_permissions", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "asset_permissions_pkey": { + "name": "asset_permissions_pkey", + "columns": [ + "identity_id", + "identity_type", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset_search": { + "name": "asset_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "asset_search_asset_id_asset_type_idx": { + "name": "asset_search_asset_id_asset_type_idx", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "asset_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pgroonga_content_index": { + "name": "pgroonga_content_index", + "columns": [ + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "pgroonga_text_full_text_search_ops_v2" + } + ], + "isUnique": false, + "concurrently": false, + "method": "pgroonga", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "most_recent_file_id": { + "name": "most_recent_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "most_recent_file_type": { + "name": "most_recent_file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "most_recent_version_number": { + "name": "most_recent_version_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slack_chat_authorization": { + "name": "slack_chat_authorization", + "type": "slack_chat_authorization_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing": { + "name": "workspace_sharing", + "type": "workspace_sharing_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "workspace_sharing_enabled_by": { + "name": "workspace_sharing_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing_enabled_at": { + "name": "workspace_sharing_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chats_created_at_idx": { + "name": "chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_created_by_idx": { + "name": "chats_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_organization_id_idx": { + "name": "chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_most_recent_file_id": { + "name": "idx_chats_most_recent_file_id", + "columns": [ + { + "expression": "most_recent_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_most_recent_file_type": { + "name": "idx_chats_most_recent_file_type", + "columns": [ + { + "expression": "most_recent_file_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chats_organization_id_fkey": { + "name": "chats_organization_id_fkey", + "tableFrom": "chats", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_created_by_fkey": { + "name": "chats_created_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "chats_updated_by_fkey": { + "name": "chats_updated_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "chats_publicly_enabled_by_fkey": { + "name": "chats_publicly_enabled_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "chats_workspace_sharing_enabled_by_fkey": { + "name": "chats_workspace_sharing_enabled_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "workspace_sharing_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_sharing": { + "name": "workspace_sharing", + "type": "workspace_sharing_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "workspace_sharing_enabled_by": { + "name": "workspace_sharing_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing_enabled_at": { + "name": "workspace_sharing_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "collections_organization_id_fkey": { + "name": "collections_organization_id_fkey", + "tableFrom": "collections", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_created_by_fkey": { + "name": "collections_created_by_fkey", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_updated_by_fkey": { + "name": "collections_updated_by_fkey", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_workspace_sharing_enabled_by_fkey": { + "name": "collections_workspace_sharing_enabled_by_fkey", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "workspace_sharing_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections_to_assets": { + "name": "collections_to_assets", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "collections_to_assets_created_by_fkey": { + "name": "collections_to_assets_created_by_fkey", + "tableFrom": "collections_to_assets", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_to_assets_updated_by_fkey": { + "name": "collections_to_assets_updated_by_fkey", + "tableFrom": "collections_to_assets", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "collections_to_assets_pkey": { + "name": "collections_to_assets_pkey", + "columns": [ + "collection_id", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_files": { + "name": "dashboard_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "version_history": { + "name": "version_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "public_password": { + "name": "public_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing": { + "name": "workspace_sharing", + "type": "workspace_sharing_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "workspace_sharing_enabled_by": { + "name": "workspace_sharing_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing_enabled_at": { + "name": "workspace_sharing_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dashboard_files_created_by_idx": { + "name": "dashboard_files_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dashboard_files_deleted_at_idx": { + "name": "dashboard_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dashboard_files_organization_id_idx": { + "name": "dashboard_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dashboard_files_created_by_fkey": { + "name": "dashboard_files_created_by_fkey", + "tableFrom": "dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboard_files_publicly_enabled_by_fkey": { + "name": "dashboard_files_publicly_enabled_by_fkey", + "tableFrom": "dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboard_files_workspace_sharing_enabled_by_fkey": { + "name": "dashboard_files_workspace_sharing_enabled_by_fkey", + "tableFrom": "dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "workspace_sharing_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_versions": { + "name": "dashboard_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_versions_dashboard_id_fkey": { + "name": "dashboard_versions_dashboard_id_fkey", + "tableFrom": "dashboard_versions", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "password_secret_id": { + "name": "password_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_publicly_enabled_by_fkey": { + "name": "dashboards_publicly_enabled_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_organization_id_fkey": { + "name": "dashboards_organization_id_fkey", + "tableFrom": "dashboards", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_created_by_fkey": { + "name": "dashboards_created_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_updated_by_fkey": { + "name": "dashboards_updated_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_sources": { + "name": "data_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "onboarding_status": { + "name": "onboarding_status", + "type": "data_source_onboarding_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notStarted'" + }, + "onboarding_error": { + "name": "onboarding_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'dev'" + } + }, + "indexes": {}, + "foreignKeys": { + "data_sources_organization_id_fkey": { + "name": "data_sources_organization_id_fkey", + "tableFrom": "data_sources", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_sources_created_by_fkey": { + "name": "data_sources_created_by_fkey", + "tableFrom": "data_sources", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "data_sources_updated_by_fkey": { + "name": "data_sources_updated_by_fkey", + "tableFrom": "data_sources", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "data_sources_name_organization_id_env_key": { + "name": "data_sources_name_organization_id_env_key", + "nullsNotDistinct": false, + "columns": [ + "name", + "organization_id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.database_metadata": { + "name": "database_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "database_metadata_data_source_id_idx": { + "name": "database_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "database_metadata_data_source_id_fkey": { + "name": "database_metadata_data_source_id_fkey", + "tableFrom": "database_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "database_metadata_data_source_id_name_key": { + "name": "database_metadata_data_source_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_columns": { + "name": "dataset_columns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nullable": { + "name": "nullable", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stored_values": { + "name": "stored_values", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stored_values_status": { + "name": "stored_values_status", + "type": "stored_values_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "stored_values_error": { + "name": "stored_values_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stored_values_count": { + "name": "stored_values_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stored_values_last_synced": { + "name": "stored_values_last_synced", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "semantic_type": { + "name": "semantic_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dim_type": { + "name": "dim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expr": { + "name": "expr", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_dataset_column_name": { + "name": "unique_dataset_column_name", + "nullsNotDistinct": false, + "columns": [ + "dataset_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_groups": { + "name": "dataset_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_groups_deleted_at_idx": { + "name": "dataset_groups_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_organization_id_idx": { + "name": "dataset_groups_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_groups_organization_id_fkey": { + "name": "dataset_groups_organization_id_fkey", + "tableFrom": "dataset_groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "dataset_groups_policy": { + "name": "dataset_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_groups_permissions": { + "name": "dataset_groups_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dataset_group_id": { + "name": "dataset_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_id": { + "name": "permission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_groups_permissions_dataset_group_id_idx": { + "name": "dataset_groups_permissions_dataset_group_id_idx", + "columns": [ + { + "expression": "dataset_group_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_permissions_organization_id_idx": { + "name": "dataset_groups_permissions_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_permissions_permission_id_idx": { + "name": "dataset_groups_permissions_permission_id_idx", + "columns": [ + { + "expression": "permission_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_groups_permissions_dataset_group_id_fkey": { + "name": "dataset_groups_permissions_dataset_group_id_fkey", + "tableFrom": "dataset_groups_permissions", + "tableTo": "dataset_groups", + "columnsFrom": [ + "dataset_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dataset_groups_permissions_organization_id_fkey": { + "name": "dataset_groups_permissions_organization_id_fkey", + "tableFrom": "dataset_groups_permissions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_dataset_group_permission": { + "name": "unique_dataset_group_permission", + "nullsNotDistinct": false, + "columns": [ + "dataset_group_id", + "permission_id", + "permission_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_permissions": { + "name": "dataset_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_id": { + "name": "permission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_permissions_dataset_id_idx": { + "name": "dataset_permissions_dataset_id_idx", + "columns": [ + { + "expression": "dataset_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_deleted_at_idx": { + "name": "dataset_permissions_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_organization_id_idx": { + "name": "dataset_permissions_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_permission_lookup_idx": { + "name": "dataset_permissions_permission_lookup_idx", + "columns": [ + { + "expression": "permission_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_permissions_organization_id_fkey": { + "name": "dataset_permissions_organization_id_fkey", + "tableFrom": "dataset_permissions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dataset_permissions_dataset_id_fkey": { + "name": "dataset_permissions_dataset_id_fkey", + "tableFrom": "dataset_permissions", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dataset_permissions_dataset_id_permission_id_permission_typ_key": { + "name": "dataset_permissions_dataset_id_permission_id_permission_typ_key", + "nullsNotDistinct": false, + "columns": [ + "dataset_id", + "permission_id", + "permission_type" + ] + } + }, + "policies": { + "dataset_permissions_policy": { + "name": "dataset_permissions_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": { + "dataset_permissions_permission_type_check": { + "name": "dataset_permissions_permission_type_check", + "value": "(permission_type)::text = ANY ((ARRAY['user'::character varying, 'dataset_group'::character varying, 'permission_group'::character varying])::text[])" + } + }, + "isRLSEnabled": false + }, + "public.datasets": { + "name": "datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "when_to_use": { + "name": "when_to_use", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when_not_to_use": { + "name": "when_not_to_use", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "dataset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "imported": { + "name": "imported", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yml_file": { + "name": "yml_file", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_identifier": { + "name": "database_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "datasets_data_source_id_fkey": { + "name": "datasets_data_source_id_fkey", + "tableFrom": "datasets", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_organization_id_fkey": { + "name": "datasets_organization_id_fkey", + "tableFrom": "datasets", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_created_by_fkey": { + "name": "datasets_created_by_fkey", + "tableFrom": "datasets", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "datasets_updated_by_fkey": { + "name": "datasets_updated_by_fkey", + "tableFrom": "datasets", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "datasets_database_name_data_source_id_key": { + "name": "datasets_database_name_data_source_id_key", + "nullsNotDistinct": false, + "columns": [ + "database_name", + "data_source_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datasets_to_dataset_groups": { + "name": "datasets_to_dataset_groups", + "schema": "", + "columns": { + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_group_id": { + "name": "dataset_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "datasets_to_dataset_groups_dataset_group_id_idx": { + "name": "datasets_to_dataset_groups_dataset_group_id_idx", + "columns": [ + { + "expression": "dataset_group_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "datasets_to_dataset_groups_dataset_id_fkey": { + "name": "datasets_to_dataset_groups_dataset_id_fkey", + "tableFrom": "datasets_to_dataset_groups", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_to_dataset_groups_dataset_group_id_fkey": { + "name": "datasets_to_dataset_groups_dataset_group_id_fkey", + "tableFrom": "datasets_to_dataset_groups", + "tableTo": "dataset_groups", + "columnsFrom": [ + "dataset_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "datasets_to_dataset_groups_pkey": { + "name": "datasets_to_dataset_groups_pkey", + "columns": [ + "dataset_id", + "dataset_group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "datasets_to_dataset_groups_policy": { + "name": "datasets_to_dataset_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datasets_to_permission_groups": { + "name": "datasets_to_permission_groups", + "schema": "", + "columns": { + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "datasets_to_permission_groups_dataset_id_fkey": { + "name": "datasets_to_permission_groups_dataset_id_fkey", + "tableFrom": "datasets_to_permission_groups", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_to_permission_groups_permission_group_id_fkey": { + "name": "datasets_to_permission_groups_permission_group_id_fkey", + "tableFrom": "datasets_to_permission_groups", + "tableTo": "permission_groups", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "datasets_to_permission_groups_pkey": { + "name": "datasets_to_permission_groups_pkey", + "columns": [ + "dataset_id", + "permission_group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "datasets_to_permission_groups_policy": { + "name": "datasets_to_permission_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.__diesel_schema_migrations": { + "name": "__diesel_schema_migrations", + "schema": "", + "columns": { + "version": { + "name": "version", + "type": "varchar(50)", + "primaryKey": true, + "notNull": true + }, + "run_on": { + "name": "run_on", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "diesel_schema_migrations_policy": { + "name": "diesel_schema_migrations_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_relationship": { + "name": "entity_relationship", + "schema": "", + "columns": { + "primary_dataset_id": { + "name": "primary_dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "foreign_dataset_id": { + "name": "foreign_dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "relationship_type": { + "name": "relationship_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "entity_relationship_pkey": { + "name": "entity_relationship_pkey", + "columns": [ + "primary_dataset_id", + "foreign_dataset_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_message": { + "name": "request_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_messages": { + "name": "response_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reasoning": { + "name": "reasoning", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "raw_llm_messages": { + "name": "raw_llm_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "final_reasoning_message": { + "name": "final_reasoning_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_completed": { + "name": "is_completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "post_processing_message": { + "name": "post_processing_message", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trigger_run_id": { + "name": "trigger_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "messages_chat_id_idx": { + "name": "messages_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_created_at_idx": { + "name": "messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_created_by_idx": { + "name": "messages_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_chat_id_fkey": { + "name": "messages_chat_id_fkey", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_created_by_fkey": { + "name": "messages_created_by_fkey", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_deprecated": { + "name": "messages_deprecated", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sent_by": { + "name": "sent_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responses": { + "name": "responses", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback": { + "name": "feedback", + "type": "message_feedback_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "verification": { + "name": "verification", + "type": "verification_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notRequested'" + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chart_config": { + "name": "chart_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "chart_recommendations": { + "name": "chart_recommendations", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "time_frame": { + "name": "time_frame", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_metadata": { + "name": "data_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "draft_session_id": { + "name": "draft_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "draft_state": { + "name": "draft_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary_question": { + "name": "summary_question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sql_evaluation_id": { + "name": "sql_evaluation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_sent_by_fkey": { + "name": "messages_sent_by_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "users", + "columnsFrom": [ + "sent_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "messages_dataset_id_fkey": { + "name": "messages_dataset_id_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_deprecated_sent_by_fkey": { + "name": "messages_deprecated_sent_by_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "users", + "columnsFrom": [ + "sent_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_to_files": { + "name": "messages_to_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "messages_files_file_id_idx": { + "name": "messages_files_file_id_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_files_message_id_idx": { + "name": "messages_files_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_to_files_message_id_fkey": { + "name": "messages_to_files_message_id_fkey", + "tableFrom": "messages_to_files", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "messages_to_files_message_id_file_id_key": { + "name": "messages_to_files_message_id_file_id_key", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "file_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_to_slack_messages": { + "name": "messages_to_slack_messages", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_message_id": { + "name": "slack_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "messages_to_slack_messages_message_id_idx": { + "name": "messages_to_slack_messages_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_to_slack_messages_slack_message_id_idx": { + "name": "messages_to_slack_messages_slack_message_id_idx", + "columns": [ + { + "expression": "slack_message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_to_slack_messages_message_id_fkey": { + "name": "messages_to_slack_messages_message_id_fkey", + "tableFrom": "messages_to_slack_messages", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_to_slack_messages_slack_message_id_fkey": { + "name": "messages_to_slack_messages_slack_message_id_fkey", + "tableFrom": "messages_to_slack_messages", + "tableTo": "slack_message_tracking", + "columnsFrom": [ + "slack_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messages_to_slack_messages_pkey": { + "name": "messages_to_slack_messages_pkey", + "columns": [ + "message_id", + "slack_message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files": { + "name": "metric_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "verification": { + "name": "verification", + "type": "verification_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notRequested'" + }, + "evaluation_obj": { + "name": "evaluation_obj", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "evaluation_summary": { + "name": "evaluation_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "evaluation_score": { + "name": "evaluation_score", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "version_history": { + "name": "version_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "data_metadata": { + "name": "data_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "public_password": { + "name": "public_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_sharing": { + "name": "workspace_sharing", + "type": "workspace_sharing_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "workspace_sharing_enabled_by": { + "name": "workspace_sharing_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_sharing_enabled_at": { + "name": "workspace_sharing_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "metric_files_created_by_idx": { + "name": "metric_files_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_data_metadata_idx": { + "name": "metric_files_data_metadata_idx", + "columns": [ + { + "expression": "data_metadata", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "jsonb_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "metric_files_deleted_at_idx": { + "name": "metric_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_organization_id_idx": { + "name": "metric_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "metric_files_created_by_fkey": { + "name": "metric_files_created_by_fkey", + "tableFrom": "metric_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "metric_files_publicly_enabled_by_fkey": { + "name": "metric_files_publicly_enabled_by_fkey", + "tableFrom": "metric_files", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "fk_data_source": { + "name": "fk_data_source", + "tableFrom": "metric_files", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "metric_files_workspace_sharing_enabled_by_fkey": { + "name": "metric_files_workspace_sharing_enabled_by_fkey", + "tableFrom": "metric_files", + "tableTo": "users", + "columnsFrom": [ + "workspace_sharing_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files_to_dashboard_files": { + "name": "metric_files_to_dashboard_files", + "schema": "", + "columns": { + "metric_file_id": { + "name": "metric_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dashboard_file_id": { + "name": "dashboard_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "metric_files_to_dashboard_files_dashboard_id_idx": { + "name": "metric_files_to_dashboard_files_dashboard_id_idx", + "columns": [ + { + "expression": "dashboard_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_to_dashboard_files_deleted_at_idx": { + "name": "metric_files_to_dashboard_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_to_dashboard_files_metric_id_idx": { + "name": "metric_files_to_dashboard_files_metric_id_idx", + "columns": [ + { + "expression": "metric_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "metric_files_to_dashboard_files_metric_file_id_fkey": { + "name": "metric_files_to_dashboard_files_metric_file_id_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "metric_files", + "columnsFrom": [ + "metric_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "metric_files_to_dashboard_files_dashboard_file_id_fkey": { + "name": "metric_files_to_dashboard_files_dashboard_file_id_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "dashboard_files", + "columnsFrom": [ + "dashboard_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "metric_files_to_dashboard_files_created_by_fkey": { + "name": "metric_files_to_dashboard_files_created_by_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "metric_files_to_dashboard_files_pkey": { + "name": "metric_files_to_dashboard_files_pkey", + "columns": [ + "metric_file_id", + "dashboard_file_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files_to_datasets": { + "name": "metric_files_to_datasets", + "schema": "", + "columns": { + "metric_file_id": { + "name": "metric_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric_version_number": { + "name": "metric_version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "fk_metric_file": { + "name": "fk_metric_file", + "tableFrom": "metric_files_to_datasets", + "tableTo": "metric_files", + "columnsFrom": [ + "metric_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_dataset": { + "name": "fk_dataset", + "tableFrom": "metric_files_to_datasets", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "metric_files_to_datasets_pkey": { + "name": "metric_files_to_datasets_pkey", + "columns": [ + "metric_file_id", + "dataset_id", + "metric_version_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payment_required": { + "name": "payment_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "domains": { + "name": "domains", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "restrict_new_user_invitations": { + "name": "restrict_new_user_invitations", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_role": { + "name": "default_role", + "type": "user_organization_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'restricted_querier'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_name_key": { + "name": "organizations_name_key", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups": { + "name": "permission_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "permission_groups_organization_id_fkey": { + "name": "permission_groups_organization_id_fkey", + "tableFrom": "permission_groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_groups_created_by_fkey": { + "name": "permission_groups_created_by_fkey", + "tableFrom": "permission_groups", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "permission_groups_updated_by_fkey": { + "name": "permission_groups_updated_by_fkey", + "tableFrom": "permission_groups", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups_to_identities": { + "name": "permission_groups_to_identities", + "schema": "", + "columns": { + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_type": { + "name": "identity_type", + "type": "identity_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "permission_groups_to_identities_created_by_fkey": { + "name": "permission_groups_to_identities_created_by_fkey", + "tableFrom": "permission_groups_to_identities", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "permission_groups_to_identities_updated_by_fkey": { + "name": "permission_groups_to_identities_updated_by_fkey", + "tableFrom": "permission_groups_to_identities", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "permission_groups_to_identities_pkey": { + "name": "permission_groups_to_identities_pkey", + "columns": [ + "permission_group_id", + "identity_id", + "identity_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups_to_users": { + "name": "permission_groups_to_users", + "schema": "", + "columns": { + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_groups_to_users_user_id_idx": { + "name": "permission_groups_to_users_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_groups_to_users_permission_group_id_fkey": { + "name": "permission_groups_to_users_permission_group_id_fkey", + "tableFrom": "permission_groups_to_users", + "tableTo": "permission_groups", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_groups_to_users_user_id_fkey": { + "name": "permission_groups_to_users_user_id_fkey", + "tableFrom": "permission_groups_to_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "permission_groups_to_users_pkey": { + "name": "permission_groups_to_users_pkey", + "columns": [ + "permission_group_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "permission_groups_to_users_policy": { + "name": "permission_groups_to_users_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schema_metadata": { + "name": "schema_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "schema_metadata_data_source_id_idx": { + "name": "schema_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schema_metadata_database_id_idx": { + "name": "schema_metadata_database_id_idx", + "columns": [ + { + "expression": "database_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schema_metadata_data_source_id_fkey": { + "name": "schema_metadata_data_source_id_fkey", + "tableFrom": "schema_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schema_metadata_database_id_fkey": { + "name": "schema_metadata_database_id_fkey", + "tableFrom": "schema_metadata", + "tableTo": "database_metadata", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "schema_metadata_data_source_id_database_id_name_key": { + "name": "schema_metadata_data_source_id_database_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "database_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_integrations": { + "name": "slack_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_state": { + "name": "oauth_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "oauth_expires_at": { + "name": "oauth_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "oauth_metadata": { + "name": "oauth_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "team_id": { + "name": "team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "team_name": { + "name": "team_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "team_domain": { + "name": "team_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_vault_key": { + "name": "token_vault_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "installed_by_slack_user_id": { + "name": "installed_by_slack_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "slack_integration_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "default_channel": { + "name": "default_channel", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "default_sharing_permissions": { + "name": "default_sharing_permissions", + "type": "slack_sharing_permission_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'shareWithChannel'" + } + }, + "indexes": { + "idx_slack_integrations_org_id": { + "name": "idx_slack_integrations_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_team_id": { + "name": "idx_slack_integrations_team_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_oauth_state": { + "name": "idx_slack_integrations_oauth_state", + "columns": [ + { + "expression": "oauth_state", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_oauth_expires": { + "name": "idx_slack_integrations_oauth_expires", + "columns": [ + { + "expression": "oauth_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_integrations_organization_id_fkey": { + "name": "slack_integrations_organization_id_fkey", + "tableFrom": "slack_integrations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_integrations_user_id_fkey": { + "name": "slack_integrations_user_id_fkey", + "tableFrom": "slack_integrations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_integrations_oauth_state_unique": { + "name": "slack_integrations_oauth_state_unique", + "nullsNotDistinct": false, + "columns": [ + "oauth_state" + ] + }, + "slack_integrations_token_vault_key_unique": { + "name": "slack_integrations_token_vault_key_unique", + "nullsNotDistinct": false, + "columns": [ + "token_vault_key" + ] + }, + "slack_integrations_org_team_key": { + "name": "slack_integrations_org_team_key", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "slack_integrations_status_check": { + "name": "slack_integrations_status_check", + "value": "(status = 'pending' AND oauth_state IS NOT NULL) OR (status != 'pending' AND team_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_message_tracking": { + "name": "slack_message_tracking", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "internal_message_id": { + "name": "internal_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slack_message_ts": { + "name": "slack_message_ts", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "message_type": { + "name": "message_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_info": { + "name": "sender_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_message_tracking_integration": { + "name": "idx_message_tracking_integration", + "columns": [ + { + "expression": "integration_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_tracking_channel": { + "name": "idx_message_tracking_channel", + "columns": [ + { + "expression": "slack_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_tracking_thread": { + "name": "idx_message_tracking_thread", + "columns": [ + { + "expression": "slack_thread_ts", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_message_tracking_integration_id_fkey": { + "name": "slack_message_tracking_integration_id_fkey", + "tableFrom": "slack_message_tracking", + "tableTo": "slack_integrations", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_message_tracking_internal_message_id_unique": { + "name": "slack_message_tracking_internal_message_id_unique", + "nullsNotDistinct": false, + "columns": [ + "internal_message_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sql_evaluations": { + "name": "sql_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "evaluation_obj": { + "name": "evaluation_obj", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "evaluation_summary": { + "name": "evaluation_summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stored_values_sync_jobs": { + "name": "stored_values_sync_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_name": { + "name": "schema_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_stored_values_sync_jobs_data_source_id": { + "name": "idx_stored_values_sync_jobs_data_source_id", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stored_values_sync_jobs_db_schema_table_column": { + "name": "idx_stored_values_sync_jobs_db_schema_table_column", + "columns": [ + { + "expression": "database_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "schema_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "table_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "column_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stored_values_sync_jobs_status": { + "name": "idx_stored_values_sync_jobs_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stored_values_sync_jobs_data_source_id_fkey": { + "name": "stored_values_sync_jobs_data_source_id_fkey", + "tableFrom": "stored_values_sync_jobs", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_metadata": { + "name": "table_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "schema_id": { + "name": "schema_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_name": { + "name": "schema_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "table_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "row_count": { + "name": "row_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "clustering_keys": { + "name": "clustering_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_metadata_data_source_id_idx": { + "name": "table_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_metadata_database_id_idx": { + "name": "table_metadata_database_id_idx", + "columns": [ + { + "expression": "database_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_metadata_schema_id_idx": { + "name": "table_metadata_schema_id_idx", + "columns": [ + { + "expression": "schema_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_metadata_data_source_id_fkey": { + "name": "table_metadata_data_source_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_metadata_database_id_fkey": { + "name": "table_metadata_database_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "database_metadata", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_metadata_schema_id_fkey": { + "name": "table_metadata_schema_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "schema_metadata", + "columnsFrom": [ + "schema_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_metadata_data_source_id_schema_id_name_key": { + "name": "table_metadata_data_source_id_schema_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "schema_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sharing_setting": { + "name": "sharing_setting", + "type": "sharing_setting_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "edit_sql": { + "name": "edit_sql", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upload_csv": { + "name": "upload_csv", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "export_assets": { + "name": "export_assets", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_slack_enabled": { + "name": "email_slack_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_fkey": { + "name": "teams_organization_id_fkey", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_created_by_fkey": { + "name": "teams_created_by_fkey", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_key": { + "name": "teams_name_key", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams_to_users": { + "name": "teams_to_users", + "schema": "", + "columns": { + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_to_users_team_id_fkey": { + "name": "teams_to_users_team_id_fkey", + "tableFrom": "teams_to_users", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_users_user_id_fkey": { + "name": "teams_to_users_user_id_fkey", + "tableFrom": "teams_to_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "teams_to_users_pkey": { + "name": "teams_to_users_pkey", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.terms": { + "name": "terms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sql_snippet": { + "name": "sql_snippet", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "terms_organization_id_fkey": { + "name": "terms_organization_id_fkey", + "tableFrom": "terms", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "terms_created_by_fkey": { + "name": "terms_created_by_fkey", + "tableFrom": "terms", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "terms_updated_by_fkey": { + "name": "terms_updated_by_fkey", + "tableFrom": "terms", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.terms_to_datasets": { + "name": "terms_to_datasets", + "schema": "", + "columns": { + "term_id": { + "name": "term_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "terms_to_datasets_term_id_fkey": { + "name": "terms_to_datasets_term_id_fkey", + "tableFrom": "terms_to_datasets", + "tableTo": "terms", + "columnsFrom": [ + "term_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "terms_to_datasets_dataset_id_fkey": { + "name": "terms_to_datasets_dataset_id_fkey", + "tableFrom": "terms_to_datasets", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "terms_to_datasets_pkey": { + "name": "terms_to_datasets_pkey", + "columns": [ + "term_id", + "dataset_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads_deprecated": { + "name": "threads_deprecated", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "password_secret_id": { + "name": "password_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state_message_id": { + "name": "state_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_thread_id": { + "name": "parent_thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "threads_created_by_fkey": { + "name": "threads_created_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_updated_by_fkey": { + "name": "threads_updated_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_publicly_enabled_by_fkey": { + "name": "threads_publicly_enabled_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_parent_thread_id_fkey": { + "name": "threads_parent_thread_id_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "threads_deprecated", + "columnsFrom": [ + "parent_thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_organization_id_fkey": { + "name": "threads_organization_id_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "threads_deprecated_created_by_fkey": { + "name": "threads_deprecated_created_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_deprecated_updated_by_fkey": { + "name": "threads_deprecated_updated_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_deprecated_publicly_enabled_by_fkey": { + "name": "threads_deprecated_publicly_enabled_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads_to_dashboards": { + "name": "threads_to_dashboards", + "schema": "", + "columns": { + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "added_by": { + "name": "added_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "threads_to_dashboards_thread_id_fkey": { + "name": "threads_to_dashboards_thread_id_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "threads_deprecated", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_to_dashboards_dashboard_id_fkey": { + "name": "threads_to_dashboards_dashboard_id_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_to_dashboards_added_by_fkey": { + "name": "threads_to_dashboards_added_by_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "threads_to_dashboards_pkey": { + "name": "threads_to_dashboards_pkey", + "columns": [ + "thread_id", + "dashboard_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_favorites": { + "name": "user_favorites", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_index": { + "name": "order_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorites_user_id_fkey": { + "name": "user_favorites_user_id_fkey", + "tableFrom": "user_favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_favorites_pkey": { + "name": "user_favorites_pkey", + "columns": [ + "user_id", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_key": { + "name": "users_email_key", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users_to_organizations": { + "name": "users_to_organizations", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "user_organization_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'querier'" + }, + "sharing_setting": { + "name": "sharing_setting", + "type": "sharing_setting_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "edit_sql": { + "name": "edit_sql", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upload_csv": { + "name": "upload_csv", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "export_assets": { + "name": "export_assets", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_slack_enabled": { + "name": "email_slack_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "deleted_by": { + "name": "deleted_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "user_organization_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "users_to_organizations_organization_id_fkey": { + "name": "users_to_organizations_organization_id_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users_to_organizations_user_id_fkey": { + "name": "users_to_organizations_user_id_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_created_by_fkey": { + "name": "users_to_organizations_created_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_updated_by_fkey": { + "name": "users_to_organizations_updated_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_deleted_by_fkey": { + "name": "users_to_organizations_deleted_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "users_to_organizations_pkey": { + "name": "users_to_organizations_pkey", + "columns": [ + "user_id", + "organization_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.asset_permission_role_enum": { + "name": "asset_permission_role_enum", + "schema": "public", + "values": [ + "owner", + "viewer", + "full_access", + "can_edit", + "can_filter", + "can_view" + ] + }, + "public.asset_type_enum": { + "name": "asset_type_enum", + "schema": "public", + "values": [ + "dashboard", + "thread", + "collection", + "chat", + "metric_file", + "dashboard_file" + ] + }, + "public.data_source_onboarding_status_enum": { + "name": "data_source_onboarding_status_enum", + "schema": "public", + "values": [ + "notStarted", + "inProgress", + "completed", + "failed" + ] + }, + "public.dataset_type_enum": { + "name": "dataset_type_enum", + "schema": "public", + "values": [ + "table", + "view", + "materializedView" + ] + }, + "public.identity_type_enum": { + "name": "identity_type_enum", + "schema": "public", + "values": [ + "user", + "team", + "organization" + ] + }, + "public.message_feedback_enum": { + "name": "message_feedback_enum", + "schema": "public", + "values": [ + "positive", + "negative" + ] + }, + "public.sharing_setting_enum": { + "name": "sharing_setting_enum", + "schema": "public", + "values": [ + "none", + "team", + "organization", + "public" + ] + }, + "public.slack_chat_authorization_enum": { + "name": "slack_chat_authorization_enum", + "schema": "public", + "values": [ + "unauthorized", + "authorized", + "auto_added" + ] + }, + "public.slack_integration_status_enum": { + "name": "slack_integration_status_enum", + "schema": "public", + "values": [ + "pending", + "active", + "failed", + "revoked" + ] + }, + "public.slack_sharing_permission_enum": { + "name": "slack_sharing_permission_enum", + "schema": "public", + "values": [ + "shareWithWorkspace", + "shareWithChannel", + "noSharing" + ] + }, + "public.stored_values_status_enum": { + "name": "stored_values_status_enum", + "schema": "public", + "values": [ + "syncing", + "success", + "failed" + ] + }, + "public.table_type_enum": { + "name": "table_type_enum", + "schema": "public", + "values": [ + "TABLE", + "VIEW", + "MATERIALIZED_VIEW", + "EXTERNAL_TABLE", + "TEMPORARY_TABLE" + ] + }, + "public.team_role_enum": { + "name": "team_role_enum", + "schema": "public", + "values": [ + "manager", + "member" + ] + }, + "public.user_organization_role_enum": { + "name": "user_organization_role_enum", + "schema": "public", + "values": [ + "workspace_admin", + "data_admin", + "querier", + "restricted_querier", + "viewer" + ] + }, + "public.user_organization_status_enum": { + "name": "user_organization_status_enum", + "schema": "public", + "values": [ + "active", + "inactive", + "pending", + "guest" + ] + }, + "public.verification_enum": { + "name": "verification_enum", + "schema": "public", + "values": [ + "verified", + "backlogged", + "inReview", + "requested", + "notRequested" + ] + }, + "public.workspace_sharing_enum": { + "name": "workspace_sharing_enum", + "schema": "public", + "values": [ + "none", + "can_view", + "can_edit", + "full_access" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/drizzle/meta/_journal.json b/packages/database/drizzle/meta/_journal.json index 7bb0a30e3..a2612c813 100644 --- a/packages/database/drizzle/meta/_journal.json +++ b/packages/database/drizzle/meta/_journal.json @@ -568,6 +568,13 @@ "when": 1752700439888, "tag": "0080_famous_emma_frost", "breakpoints": true + }, + { + "idx": 81, + "version": "7", + "when": 1752770723373, + "tag": "0081_curly_silvermane", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/database/src/schema.ts b/packages/database/src/schema.ts index aba979f0c..d8f7bc59d 100644 --- a/packages/database/src/schema.ts +++ b/packages/database/src/schema.ts @@ -105,6 +105,13 @@ export const slackSharingPermissionEnum = pgEnum('slack_sharing_permission_enum' 'noSharing', ]); +export const workspaceSharingEnum = pgEnum('workspace_sharing_enum', [ + 'none', + 'can_view', + 'can_edit', + 'full_access', +]); + export const apiKeys = pgTable( 'api_keys', { @@ -258,6 +265,9 @@ export const collections = pgTable( .notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), organizationId: uuid('organization_id').notNull(), + workspaceSharing: workspaceSharingEnum('workspace_sharing').default('none').notNull(), + workspaceSharingEnabledBy: uuid('workspace_sharing_enabled_by'), + workspaceSharingEnabledAt: timestamp('workspace_sharing_enabled_at', { withTimezone: true, mode: 'string' }), }, (table) => [ foreignKey({ @@ -275,6 +285,11 @@ export const collections = pgTable( foreignColumns: [users.id], name: 'collections_updated_by_fkey', }).onUpdate('cascade'), + foreignKey({ + columns: [table.workspaceSharingEnabledBy], + foreignColumns: [users.id], + name: 'collections_workspace_sharing_enabled_by_fkey', + }).onUpdate('cascade'), ] ); @@ -926,6 +941,9 @@ export const dashboardFiles = pgTable( }), versionHistory: jsonb('version_history').default({}).notNull(), publicPassword: text('public_password'), + workspaceSharing: workspaceSharingEnum('workspace_sharing').default('none').notNull(), + workspaceSharingEnabledBy: uuid('workspace_sharing_enabled_by'), + workspaceSharingEnabledAt: timestamp('workspace_sharing_enabled_at', { withTimezone: true, mode: 'string' }), }, (table) => [ index('dashboard_files_created_by_idx').using( @@ -950,6 +968,11 @@ export const dashboardFiles = pgTable( foreignColumns: [users.id], name: 'dashboard_files_publicly_enabled_by_fkey', }).onUpdate('cascade'), + foreignKey({ + columns: [table.workspaceSharingEnabledBy], + foreignColumns: [users.id], + name: 'dashboard_files_workspace_sharing_enabled_by_fkey', + }).onUpdate('cascade'), ] ); @@ -980,6 +1003,9 @@ export const chats = pgTable( slackChatAuthorization: slackChatAuthorizationEnum('slack_chat_authorization'), slackThreadTs: text('slack_thread_ts'), slackChannelId: text('slack_channel_id'), + workspaceSharing: workspaceSharingEnum('workspace_sharing').default('none').notNull(), + workspaceSharingEnabledBy: uuid('workspace_sharing_enabled_by'), + workspaceSharingEnabledAt: timestamp('workspace_sharing_enabled_at', { withTimezone: true, mode: 'string' }), }, (table) => [ index('chats_created_at_idx').using( @@ -1019,6 +1045,11 @@ export const chats = pgTable( foreignColumns: [users.id], name: 'chats_publicly_enabled_by_fkey', }).onUpdate('cascade'), + foreignKey({ + columns: [table.workspaceSharingEnabledBy], + foreignColumns: [users.id], + name: 'chats_workspace_sharing_enabled_by_fkey', + }).onUpdate('cascade'), ] ); @@ -1116,6 +1147,9 @@ export const metricFiles = pgTable( dataMetadata: jsonb('data_metadata'), publicPassword: text('public_password'), dataSourceId: uuid('data_source_id').notNull(), + workspaceSharing: workspaceSharingEnum('workspace_sharing').default('none').notNull(), + workspaceSharingEnabledBy: uuid('workspace_sharing_enabled_by'), + workspaceSharingEnabledAt: timestamp('workspace_sharing_enabled_at', { withTimezone: true, mode: 'string' }), }, (table) => [ index('metric_files_created_by_idx').using( @@ -1149,6 +1183,11 @@ export const metricFiles = pgTable( foreignColumns: [dataSources.id], name: 'fk_data_source', }), + foreignKey({ + columns: [table.workspaceSharingEnabledBy], + foreignColumns: [users.id], + name: 'metric_files_workspace_sharing_enabled_by_fkey', + }).onUpdate('cascade'), ] ); diff --git a/packages/slack/src/index.ts b/packages/slack/src/index.ts index f32b62228..c047a7e81 100644 --- a/packages/slack/src/index.ts +++ b/packages/slack/src/index.ts @@ -39,6 +39,7 @@ export { MessageTrackingDataSchema } from './interfaces/message-tracking'; export * from './utils/validation-helpers'; export * from './utils/message-formatter'; export * from './utils/oauth-helpers'; +export { convertMarkdownToSlack } from './utils/markdown-to-slack'; // Reactions export { addReaction, removeReaction, getReactions } from './reactions';