mirror of https://github.com/buster-so/buster.git
199 lines
7.2 KiB
Rust
199 lines
7.2 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use chrono::Utc;
|
|
use diesel::{ExpressionMethods, QueryDsl};
|
|
use diesel_async::RunQueryDsl;
|
|
use middleware::AuthenticatedUser;
|
|
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::helpers::chats::fetch_chat_with_permission;
|
|
use database::models::{AssetPermission, Chat, Message, MessageToFile};
|
|
use database::pool::get_pg_pool;
|
|
use database::schema::{asset_permissions, chats, messages, messages_to_files};
|
|
use sharing::check_permission_access;
|
|
|
|
/// Handler function to duplicate an existing chat
|
|
///
|
|
/// # Arguments
|
|
/// * `chat_id` - UUID of the source chat to duplicate
|
|
/// * `message_id` - Optional UUID of a message in the chat; if provided, only messages created at or after this message will be duplicated
|
|
/// * `user` - The authenticated user making the request
|
|
///
|
|
/// # Returns
|
|
/// * `Result<ChatWithMessages>` - The newly created chat with all duplicated messages
|
|
pub async fn duplicate_chat_handler(
|
|
chat_id: &Uuid,
|
|
message_id: Option<&Uuid>,
|
|
user: &AuthenticatedUser,
|
|
) -> Result<ChatWithMessages> {
|
|
// First check if the user has permission to view the source chat
|
|
let chat_with_permission = fetch_chat_with_permission(chat_id, &user.id).await?;
|
|
|
|
// If chat not found, return error
|
|
let chat_with_permission = match chat_with_permission {
|
|
Some(cwp) => cwp,
|
|
None => return Err(anyhow!("Chat not found")),
|
|
};
|
|
|
|
// Check if user has permission to view the chat
|
|
// Users need at least CanView permission or any higher permission
|
|
let has_permission = check_permission_access(
|
|
chat_with_permission.permission,
|
|
&[
|
|
AssetPermissionRole::CanView,
|
|
AssetPermissionRole::CanEdit,
|
|
AssetPermissionRole::FullAccess,
|
|
AssetPermissionRole::Owner,
|
|
],
|
|
chat_with_permission.chat.organization_id,
|
|
&user.organizations,
|
|
);
|
|
|
|
// If user is the creator, they automatically have access
|
|
let is_creator = chat_with_permission.chat.created_by == user.id;
|
|
|
|
if !has_permission && !is_creator {
|
|
return Err(anyhow!("You don't have permission to view this chat"));
|
|
}
|
|
|
|
let mut conn = get_pg_pool().get().await?;
|
|
|
|
// 1. Create a new chat record
|
|
let source_chat = chat_with_permission.chat;
|
|
let new_chat_id = Uuid::new_v4();
|
|
let now = Utc::now();
|
|
|
|
// Append (Copy) to the title
|
|
let new_title = format!("{} (Copy)", source_chat.title);
|
|
|
|
let new_chat = Chat {
|
|
id: new_chat_id,
|
|
title: new_title,
|
|
organization_id: source_chat.organization_id,
|
|
created_at: now,
|
|
updated_at: now,
|
|
deleted_at: None,
|
|
created_by: user.id,
|
|
updated_by: user.id,
|
|
publicly_accessible: false, // Start with private access
|
|
publicly_enabled_by: None,
|
|
public_expiry_date: None,
|
|
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,
|
|
};
|
|
|
|
// Insert the new chat record
|
|
diesel::insert_into(chats::table)
|
|
.values(&new_chat)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
// 2. Set permissions for the new chat (owner for the current user)
|
|
let new_permission = AssetPermission {
|
|
identity_id: user.id,
|
|
identity_type: IdentityType::User,
|
|
asset_id: new_chat_id,
|
|
asset_type: AssetType::Chat,
|
|
role: AssetPermissionRole::Owner,
|
|
created_at: now,
|
|
updated_at: now,
|
|
deleted_at: None,
|
|
created_by: user.id,
|
|
updated_by: user.id,
|
|
};
|
|
|
|
diesel::insert_into(asset_permissions::table)
|
|
.values(&new_permission)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
// 3. Determine which messages to duplicate based on message_id parameter
|
|
let messages_to_duplicate = match message_id {
|
|
Some(msg_id) => {
|
|
// Get the specific message and check if it belongs to the chat
|
|
let message = messages::table
|
|
.filter(messages::id.eq(msg_id))
|
|
.filter(messages::chat_id.eq(chat_id))
|
|
.filter(messages::deleted_at.is_null())
|
|
.first::<Message>(&mut conn)
|
|
.await?;
|
|
|
|
// Get this message and all older messages (messages created at or before this message)
|
|
messages::table
|
|
.filter(messages::chat_id.eq(chat_id))
|
|
.filter(messages::deleted_at.is_null())
|
|
.filter(messages::created_at.le(message.created_at))
|
|
.order_by(messages::created_at.asc())
|
|
.load::<Message>(&mut conn)
|
|
.await?
|
|
}
|
|
None => {
|
|
// Get all non-deleted messages from the chat
|
|
messages::table
|
|
.filter(messages::chat_id.eq(chat_id))
|
|
.filter(messages::deleted_at.is_null())
|
|
.order_by(messages::created_at.asc())
|
|
.load::<Message>(&mut conn)
|
|
.await?
|
|
}
|
|
};
|
|
|
|
// 4. Duplicate each message
|
|
for source_message in messages_to_duplicate {
|
|
let new_message_id = Uuid::new_v4();
|
|
|
|
// Create a new message record
|
|
let new_message = Message {
|
|
id: new_message_id,
|
|
request_message: source_message.request_message.clone(),
|
|
response_messages: source_message.response_messages.clone(),
|
|
reasoning: source_message.reasoning.clone(),
|
|
title: source_message.title.clone(),
|
|
raw_llm_messages: source_message.raw_llm_messages.clone(),
|
|
final_reasoning_message: source_message.final_reasoning_message.clone(),
|
|
chat_id: new_chat_id,
|
|
created_at: source_message.created_at,
|
|
updated_at: source_message.updated_at,
|
|
deleted_at: None,
|
|
created_by: user.id,
|
|
feedback: source_message.feedback.clone(),
|
|
};
|
|
|
|
// Insert the new message record
|
|
diesel::insert_into(messages::table)
|
|
.values(&new_message)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
// 5. Duplicate associated file references with is_duplicate flag
|
|
let file_references = messages_to_files::table
|
|
.filter(messages_to_files::message_id.eq(source_message.id))
|
|
.filter(messages_to_files::deleted_at.is_null())
|
|
.load::<MessageToFile>(&mut conn)
|
|
.await?;
|
|
|
|
for file_ref in file_references {
|
|
let new_file_ref = MessageToFile {
|
|
id: Uuid::new_v4(),
|
|
message_id: new_message_id,
|
|
file_id: file_ref.file_id,
|
|
created_at: now,
|
|
updated_at: now,
|
|
deleted_at: None,
|
|
is_duplicate: true, // Mark as duplicate
|
|
version_number: file_ref.version_number,
|
|
};
|
|
|
|
diesel::insert_into(messages_to_files::table)
|
|
.values(&new_file_ref)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// 6. Return the new chat with all messages
|
|
get_chat_handler(&new_chat_id, user, false).await
|
|
} |