This commit is contained in:
parent
e15da79204
commit
50bfad7642
11 changed files with 311 additions and 92 deletions
|
@ -5,19 +5,21 @@ FOR EACH item IN items
|
||||||
let website = WEBSITE OF item.company
|
let website = WEBSITE OF item.company
|
||||||
PRINT website
|
PRINT website
|
||||||
|
|
||||||
WAIT 10
|
|
||||||
let page = GET website
|
let page = GET website
|
||||||
|
|
||||||
let prompt = "Create a website for " + item.company + " with the following details: " + page
|
let prompt = "Build the same simulator , but for " + item.company + " using just *content about the company* from its website, so it is possible to create a good and useful emulator in the same langue as the content: " + page
|
||||||
|
|
||||||
let alias = LLM "Return a single word for " + item.company + " like a token, no spaces, no special characters, no numbers, no uppercase letters."
|
let alias = LLM "Return a single word for " + item.company + " like a token, no spaces, no special characters, no numbers, no uppercase letters."
|
||||||
|
|
||||||
CREATE SITE item.company + "bot", item.company, website, "site", prompt
|
|
||||||
|
|
||||||
let to = item.emailcto
|
let to = item.emailcto
|
||||||
let subject = "Simulador criado " + item.company
|
let subject = "General Bots"
|
||||||
let body = "O simulador " + item.company + " foi criado com sucesso. Acesse o site: " + item.company + "bot"
|
let body = "Oi, tudo bem? Criamos o simulador " + alias + " especificamente para vocês!" + "\n\n Acesse o site: https://sites.pragmatismo.com.br/" + alias + "\n\n" + "Para acessar o simulador, clique no link acima ou copie e cole no seu navegador." + "\n\n" + "Para iniciar, clique no ícone de Play." + "\n\n" + "Atenciosamente,\nDário Vieira"
|
||||||
|
|
||||||
CREATE_DRAFT to, subject, body
|
CREATE_DRAFT to, subject, body
|
||||||
|
|
||||||
NEXT item
|
|
||||||
|
|
||||||
|
NEXT item
|
|
@ -8,6 +8,7 @@ pub struct AppConfig {
|
||||||
pub database_custom: DatabaseConfig,
|
pub database_custom: DatabaseConfig,
|
||||||
pub email: EmailConfig,
|
pub email: EmailConfig,
|
||||||
pub ai: AIConfig,
|
pub ai: AIConfig,
|
||||||
|
pub site_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -141,6 +142,7 @@ impl AppConfig {
|
||||||
database_custom,
|
database_custom,
|
||||||
email,
|
email,
|
||||||
ai,
|
ai,
|
||||||
|
site_path: env::var("SITES_ROOT").unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -289,6 +289,83 @@ pub async fn get_latest_email_from(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_latest_sent_to(
|
||||||
|
email_config: &EmailConfig,
|
||||||
|
to_email: &str,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
// Establish connection
|
||||||
|
let tls = native_tls::TlsConnector::builder().build()?;
|
||||||
|
let client = imap::connect(
|
||||||
|
(email_config.server.as_str(), 993),
|
||||||
|
email_config.server.as_str(),
|
||||||
|
&tls,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Login
|
||||||
|
let mut session = client.login(&email_config.username, &email_config.password)
|
||||||
|
.map_err(|e| format!("Login failed: {:?}", e))?;
|
||||||
|
|
||||||
|
// Try to select Archive folder first, then fall back to INBOX
|
||||||
|
if session.select("Sent").is_err() {
|
||||||
|
session.select("Sent Items")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for emails from the specified sender
|
||||||
|
let search_query = format!("TO \"{}\"", to_email);
|
||||||
|
let messages = session.search(&search_query)?;
|
||||||
|
|
||||||
|
if messages.is_empty() {
|
||||||
|
session.logout()?;
|
||||||
|
return Err(format!("No emails found to {}", to_email).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest message (highest sequence number)
|
||||||
|
let latest_seq = messages.iter().max().unwrap();
|
||||||
|
|
||||||
|
// Fetch the entire message
|
||||||
|
let messages = session.fetch(latest_seq.to_string(), "RFC822")?;
|
||||||
|
|
||||||
|
let mut email_text = String::new();
|
||||||
|
|
||||||
|
for msg in messages.iter() {
|
||||||
|
let body = msg.body().ok_or("No body found in email")?;
|
||||||
|
|
||||||
|
// Parse the complete email message
|
||||||
|
let parsed = parse_mail(body)?;
|
||||||
|
|
||||||
|
// Extract headers
|
||||||
|
let headers = parsed.get_headers();
|
||||||
|
let subject = headers.get_first_value("Subject").unwrap_or_default();
|
||||||
|
let from = headers.get_first_value("From").unwrap_or_default();
|
||||||
|
let date = headers.get_first_value("Date").unwrap_or_default();
|
||||||
|
let to = headers.get_first_value("To").unwrap_or_default();
|
||||||
|
|
||||||
|
// Extract body text
|
||||||
|
let body_text = if let Some(body_part) = parsed.subparts.iter().find(|p| p.ctype.mimetype == "text/plain") {
|
||||||
|
body_part.get_body().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
parsed.get_body().unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format the email text ready for reply with headers
|
||||||
|
email_text = format!(
|
||||||
|
"--- Original Message ---\nFrom: {}\nTo: {}\nDate: {}\nSubject: {}\n\n{}\n\n--- Reply Above This Line ---\n\n",
|
||||||
|
from, to, date, subject, body_text
|
||||||
|
);
|
||||||
|
|
||||||
|
break; // We only want the first (and should be only) message
|
||||||
|
}
|
||||||
|
|
||||||
|
session.logout()?;
|
||||||
|
|
||||||
|
if email_text.is_empty() {
|
||||||
|
Err("Failed to extract email content".into())
|
||||||
|
} else {
|
||||||
|
Ok(email_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_latest_email_from_sender(
|
pub async fn fetch_latest_email_from_sender(
|
||||||
email_config: &EmailConfig,
|
email_config: &EmailConfig,
|
||||||
from_email: &str,
|
from_email: &str,
|
||||||
|
@ -366,6 +443,7 @@ pub async fn fetch_latest_email_from_sender(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[actix_web::post("/emails/send")]
|
#[actix_web::post("/emails/send")]
|
||||||
pub async fn send_email(
|
pub async fn send_email(
|
||||||
payload: web::Json<(String, String, String)>,
|
payload: web::Json<(String, String, String)>,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::services::email::SaveDraftRequest;
|
use crate::services::email::{fetch_latest_sent_to, SaveDraftRequest};
|
||||||
use crate::services::email::{fetch_latest_email_from_sender, save_email_draft};
|
use crate::services::email::{save_email_draft};
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
use rhai::Dynamic;
|
use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
|
@ -35,10 +35,10 @@ async fn execute_create_draft(
|
||||||
subject: &str,
|
subject: &str,
|
||||||
reply_text: &str,
|
reply_text: &str,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to).await;
|
let get_result = fetch_latest_sent_to(&state.config.clone().unwrap().email, to).await;
|
||||||
let email_body = if let Ok(get_result_str) = get_result {
|
let email_body = if let Ok(get_result_str) = get_result {
|
||||||
if !get_result_str.is_empty() {
|
if !get_result_str.is_empty() {
|
||||||
get_result_str + reply_text
|
reply_text.to_string() + get_result_str.as_str()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::{ PathBuf};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
use crate::services::utils;
|
use crate::services::utils;
|
||||||
|
@ -12,26 +13,25 @@ pub fn create_site_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(
|
||||||
&[
|
&[
|
||||||
"CREATE", "SITE", "$expr$", ",", "$expr$", ",", "$expr$", ",", "$expr$", ",",
|
"CREATE_SITE", "$expr$", ",", "$expr$", ",", "$expr$",
|
||||||
"$expr$",
|
|
||||||
],
|
],
|
||||||
true, // Statement
|
true,
|
||||||
move |context, inputs| {
|
move |context, inputs| {
|
||||||
if inputs.len() < 5 {
|
if inputs.len() < 3 {
|
||||||
return Err("Not enough arguments for CREATE SITE".into());
|
return Err("Not enough arguments for CREATE SITE".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _name = context.eval_expression_tree(&inputs[0])?;
|
let alias = context.eval_expression_tree(&inputs[0])?;
|
||||||
|
let template_dir = context.eval_expression_tree(&inputs[1])?;
|
||||||
let _website = context.eval_expression_tree(&inputs[2])?;
|
let prompt = context.eval_expression_tree(&inputs[2])?;
|
||||||
let _template = context.eval_expression_tree(&inputs[3])?;
|
|
||||||
let prompt = context.eval_expression_tree(&inputs[4])?;
|
let config = state_clone.config.as_ref().expect("Config must be initialized").clone();
|
||||||
let ai_config = state_clone.config.as_ref().expect("Config must be initialized").ai.clone();
|
|
||||||
// Use the same pattern as find_keyword
|
let fut = create_site(&config, alias, template_dir, prompt);
|
||||||
let fut = create_site(&ai_config, _name, prompt);
|
|
||||||
let result =
|
let result =
|
||||||
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
|
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
|
||||||
.map_err(|e| format!("HTTP request failed: {}", e))?;
|
.map_err(|e| format!("Site creation failed: {}", e))?;
|
||||||
|
|
||||||
Ok(Dynamic::from(result))
|
Ok(Dynamic::from(result))
|
||||||
},
|
},
|
||||||
|
@ -40,26 +40,51 @@ pub fn create_site_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_site(
|
async fn create_site(
|
||||||
ai_config: &crate::services::config::AIConfig,
|
config: &crate::services::config::AppConfig,
|
||||||
_name: Dynamic,
|
alias: Dynamic,
|
||||||
|
template_dir: Dynamic,
|
||||||
prompt: Dynamic,
|
prompt: Dynamic,
|
||||||
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
|
// Convert paths to platform-specific format
|
||||||
|
let base_path = PathBuf::from(&config.site_path);
|
||||||
|
let template_path = base_path.join(template_dir.to_string());
|
||||||
|
let alias_path = base_path.join(alias.to_string());
|
||||||
|
|
||||||
// Call the LLM to generate the HTML contents
|
// Create destination directory
|
||||||
let llm_result = utils::call_llm(&prompt.to_string(), &ai_config).await?;
|
fs::create_dir_all(&alias_path).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Create the directory structure
|
// Process all HTML files in template directory
|
||||||
let base_path = "/opt/gbo/tenants/pragmatismo/proxy/data/websites/sites.pragmatismo.com.br";
|
let mut combined_content = String::new();
|
||||||
let site_name = format!("{}", _name.to_string());
|
|
||||||
let full_path = format!("{}/{}", base_path, site_name);
|
for entry in fs::read_dir(&template_path).map_err(|e| e.to_string())? {
|
||||||
|
let entry = entry.map_err(|e| e.to_string())?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.extension().map_or(false, |ext| ext == "html") {
|
||||||
|
let mut file = fs::File::open(&path).map_err(|e| e.to_string())?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
combined_content.push_str(&contents);
|
||||||
|
combined_content.push_str("\n\n--- TEMPLATE SEPARATOR ---\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create directory if it doesn't exist
|
// Combine template content with prompt
|
||||||
fs::create_dir_all(&full_path).map_err(|e| e.to_string())?;
|
let full_prompt = format!(
|
||||||
|
"TEMPLATE FILES:\n{}\n\nPROMPT: {}\n\nGenerate a new HTML file cloning all previous TEMPLATE (keeping only the local _assets libraries use, no external resources), but turning this into this prompt:",
|
||||||
|
combined_content,
|
||||||
|
prompt.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
// Write the HTML file
|
// Call LLM with the combined prompt
|
||||||
let index_path = Path::new(&full_path).join("index.html");
|
println!("Asking LLM to create site.");
|
||||||
|
let llm_result = utils::call_llm(&full_prompt, &config.ai).await?;
|
||||||
|
|
||||||
|
// Write the generated HTML file
|
||||||
|
let index_path = alias_path.join("index.html");
|
||||||
fs::write(index_path, llm_result).map_err(|e| e.to_string())?;
|
fs::write(index_path, llm_result).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
println!("Site created at: {}", full_path);
|
println!("Site created at: {}", alias_path.display());
|
||||||
Ok(full_path)
|
Ok(alias_path.to_string_lossy().into_owned())
|
||||||
}
|
}
|
|
@ -2,7 +2,6 @@ use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use sqlx::{PgPool};
|
use sqlx::{PgPool};
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
use crate::services::utils;
|
use crate::services::utils;
|
||||||
|
@ -54,7 +53,7 @@ pub async fn execute_find(
|
||||||
table_str, filter_str
|
table_str, filter_str
|
||||||
);
|
);
|
||||||
|
|
||||||
let (where_clause, params) = parse_filter(filter_str).map_err(|e| e.to_string())?;
|
let (where_clause, params) = utils::parse_filter(filter_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let query = format!(
|
let query = format!(
|
||||||
"SELECT * FROM {} WHERE {} LIMIT 10",
|
"SELECT * FROM {} WHERE {} LIMIT 10",
|
||||||
|
@ -87,24 +86,3 @@ pub async fn execute_find(
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to parse the filter string into SQL WHERE clause and parameters
|
|
||||||
fn parse_filter(filter_str: &str) -> Result<(String, Vec<String>), Box<dyn Error>> {
|
|
||||||
let parts: Vec<&str> = filter_str.split('=').collect();
|
|
||||||
if parts.len() != 2 {
|
|
||||||
return Err("Invalid filter format. Expected 'KEY=VALUE'".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let column = parts[0].trim();
|
|
||||||
let value = parts[1].trim();
|
|
||||||
|
|
||||||
// Validate column name to prevent SQL injection
|
|
||||||
if !column
|
|
||||||
.chars()
|
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
|
||||||
{
|
|
||||||
return Err("Invalid column name in filter".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the parameterized query part and the value separately
|
|
||||||
Ok((format!("{} = $1", column), vec![value.to_string()]))
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
use reqwest;
|
use reqwest;
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use scraper::{Html, Selector};
|
||||||
|
|
||||||
|
|
||||||
pub fn get_keyword(_state: &AppState, engine: &mut Engine) {
|
pub fn get_keyword(_state: &AppState, engine: &mut Engine) {
|
||||||
engine.register_custom_syntax(
|
engine.register_custom_syntax(
|
||||||
|
@ -29,7 +31,7 @@ pub fn get_keyword(_state: &AppState, engine: &mut Engine) {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_get(url: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
|
pub async fn _execute_get(url: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
println!("Starting execute_get with URL: {}", url);
|
println!("Starting execute_get with URL: {}", url);
|
||||||
|
|
||||||
let response = reqwest::get(url).await?;
|
let response = reqwest::get(url).await?;
|
||||||
|
@ -37,4 +39,28 @@ pub async fn execute_get(url: &str) -> Result<String, Box<dyn Error + Send + Syn
|
||||||
|
|
||||||
println!("GET request successful, got {} bytes", content.len());
|
println!("GET request successful, got {} bytes", content.len());
|
||||||
Ok(format!("Secure content fetched: {}", content))
|
Ok(format!("Secure content fetched: {}", content))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn execute_get(url: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
|
println!("Starting execute_get with URL: {}", url);
|
||||||
|
|
||||||
|
let response = reqwest::get(url).await?;
|
||||||
|
let html_content = response.text().await?;
|
||||||
|
|
||||||
|
// Parse HTML and extract text
|
||||||
|
let document = Html::parse_document(&html_content);
|
||||||
|
let selector = Selector::parse("body").unwrap(); // Focus on body content
|
||||||
|
let body = document.select(&selector).next().unwrap();
|
||||||
|
let text_content = body.text().collect::<Vec<_>>().join(" ");
|
||||||
|
|
||||||
|
// Clean up the text (remove extra whitespace, newlines, etc.)
|
||||||
|
let cleaned_text = text_content
|
||||||
|
.replace('\n', " ")
|
||||||
|
.replace('\t', " ")
|
||||||
|
.split_whitespace()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
println!("GET request successful, extracted {} characters of text", cleaned_text.len());
|
||||||
|
Ok(cleaned_text)
|
||||||
|
}
|
||||||
|
|
|
@ -77,7 +77,8 @@ async fn perform_search(
|
||||||
|
|
||||||
// Extract results
|
// Extract results
|
||||||
let results = extract_search_results(&driver).await?;
|
let results = extract_search_results(&driver).await?;
|
||||||
|
driver.quit().await?;
|
||||||
|
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
Ok(results[0].clone())
|
Ok(results[0].clone())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub fn llm_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
let ai_config = state.config.clone().unwrap().ai.clone();
|
let ai_config = state.config.clone().unwrap().ai.clone();
|
||||||
|
|
||||||
engine.register_custom_syntax(
|
engine.register_custom_syntax(
|
||||||
&["LLM", "$string$"], // Syntax: LLM "text to process"
|
&["LLM", "$expr$"], // Syntax: LLM "text to process"
|
||||||
false, // Expression, not statement
|
false, // Expression, not statement
|
||||||
move |context, inputs| {
|
move |context, inputs| {
|
||||||
let text = context.eval_expression_tree(&inputs[0])?;
|
let text = context.eval_expression_tree(&inputs[0])?;
|
||||||
|
@ -16,7 +16,8 @@ pub fn llm_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
|
|
||||||
// Use the same pattern as GET
|
// Use the same pattern as GET
|
||||||
|
|
||||||
let fut = call_llm(&text_str, &ai_config);
|
let fut = call_llm(
|
||||||
|
&text_str, &ai_config);
|
||||||
let result = tokio::task::block_in_place(|| {
|
let result = tokio::task::block_in_place(|| {
|
||||||
tokio::runtime::Handle::current().block_on(fut)
|
tokio::runtime::Handle::current().block_on(fut)
|
||||||
}).map_err(|e| format!("LLM call failed: {}", e))?;
|
}).map_err(|e| format!("LLM call failed: {}", e))?;
|
||||||
|
|
|
@ -1,36 +1,120 @@
|
||||||
use rhai::Dynamic;
|
use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde_json::json;
|
use serde_json::{json, Value};
|
||||||
|
use sqlx::{PgPool};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
|
use crate::services::utils;
|
||||||
|
|
||||||
|
|
||||||
pub fn set_keyword(_state: &AppState, engine: &mut Engine) {
|
pub fn set_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
|
let db = state.db_custom.clone();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(&["SET", "$expr$", ",", "$expr$", ",", "$expr$"], false, {
|
||||||
&["SET", "$expr$", ",", "$expr$", ",", "$expr$"],
|
let db = db.clone();
|
||||||
true, // Statement
|
|
||||||
|context, inputs| {
|
|
||||||
let table_name = context.eval_expression_tree(&inputs[0])?;
|
|
||||||
let key_value = context.eval_expression_tree(&inputs[1])?;
|
|
||||||
let value = context.eval_expression_tree(&inputs[2])?;
|
|
||||||
|
|
||||||
let table_str = table_name.to_string();
|
move |context, inputs| {
|
||||||
let key_str = key_value.to_string();
|
let table_name = context.eval_expression_tree(&inputs[0])?;
|
||||||
let value_str = value.to_string();
|
let filter = context.eval_expression_tree(&inputs[1])?;
|
||||||
|
let updates = context.eval_expression_tree(&inputs[2])?;
|
||||||
|
let binding = db.as_ref().unwrap();
|
||||||
|
|
||||||
let result = json!({
|
// Use the current async context instead of creating a new runtime
|
||||||
"command": "set",
|
let binding2 = table_name.to_string();
|
||||||
"status": "success",
|
let binding3 = filter.to_string();
|
||||||
"table": table_str,
|
let binding4 = updates.to_string();
|
||||||
"key": key_str,
|
let fut = execute_set(binding, &binding2, &binding3, &binding4);
|
||||||
"value": value_str
|
|
||||||
});
|
|
||||||
println!("SET executed: {}", result.to_string());
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
|
// Use tokio::task::block_in_place + tokio::runtime::Handle::current().block_on
|
||||||
|
let result =
|
||||||
|
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
|
||||||
|
.map_err(|e| format!("DB error: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(rows_affected) = result.get("rows_affected") {
|
||||||
|
Ok(Dynamic::from(rows_affected.as_i64().unwrap_or(0)))
|
||||||
|
} else {
|
||||||
|
Err("No rows affected".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn execute_set(
|
||||||
|
pool: &PgPool,
|
||||||
|
table_str: &str,
|
||||||
|
filter_str: &str,
|
||||||
|
updates_str: &str,
|
||||||
|
) -> Result<Value, String> {
|
||||||
|
println!(
|
||||||
|
"Starting execute_set with table: {}, filter: {}, updates: {}",
|
||||||
|
table_str, filter_str, updates_str
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse the filter condition
|
||||||
|
let (where_clause, filter_params) = utils::parse_filter(filter_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Parse the updates
|
||||||
|
let (set_clause, update_params) = parse_updates(updates_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Combine all parameters (updates first, then filter)
|
||||||
|
let mut params = update_params;
|
||||||
|
params.extend(filter_params);
|
||||||
|
|
||||||
|
let query = format!(
|
||||||
|
"UPDATE {} SET {} WHERE {}",
|
||||||
|
table_str, set_clause, where_clause
|
||||||
|
);
|
||||||
|
println!("Executing query: {}", query);
|
||||||
|
|
||||||
|
// Execute the update
|
||||||
|
let result = sqlx::query(&query)
|
||||||
|
.bind(¶ms[0]) // First update value
|
||||||
|
.bind(¶ms[1]) // Second update value if exists
|
||||||
|
.bind(¶ms[2]) // Filter value
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("SQL execution error: {}", e);
|
||||||
|
e.to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
println!("Update successful, affected {} rows", result.rows_affected());
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"command": "set",
|
||||||
|
"table": table_str,
|
||||||
|
"filter": filter_str,
|
||||||
|
"updates": updates_str,
|
||||||
|
"rows_affected": result.rows_affected()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to parse the updates string into SQL SET clause and parameters
|
||||||
|
fn parse_updates(updates_str: &str) -> Result<(String, Vec<String>), Box<dyn Error>> {
|
||||||
|
let mut set_clauses = Vec::new();
|
||||||
|
let mut params = Vec::new();
|
||||||
|
|
||||||
|
// Split multiple updates by comma
|
||||||
|
for update in updates_str.split(',') {
|
||||||
|
let parts: Vec<&str> = update.split('=').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err("Invalid update format. Expected 'KEY=VALUE'".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let column = parts[0].trim();
|
||||||
|
let value = parts[1].trim();
|
||||||
|
|
||||||
|
// Validate column name to prevent SQL injection
|
||||||
|
if !column.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
|
||||||
|
return Err("Invalid column name in update".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
set_clauses.push(format!("{} = ${}", column, set_clauses.len() + 1));
|
||||||
|
params.push(value.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((set_clauses.join(", "), params))
|
||||||
|
}
|
|
@ -216,3 +216,25 @@ pub async fn download_file(url: &str, output_path: &str) -> Result<(), Box<dyn s
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to parse the filter string into SQL WHERE clause and parameters
|
||||||
|
pub fn parse_filter(filter_str: &str) -> Result<(String, Vec<String>), Box<dyn Error>> {
|
||||||
|
let parts: Vec<&str> = filter_str.split('=').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err("Invalid filter format. Expected 'KEY=VALUE'".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let column = parts[0].trim();
|
||||||
|
let value = parts[1].trim();
|
||||||
|
|
||||||
|
// Validate column name to prevent SQL injection
|
||||||
|
if !column
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||||
|
{
|
||||||
|
return Err("Invalid column name in filter".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the parameterized query part and the value separately
|
||||||
|
Ok((format!("{} = $1", column), vec![value.to_string()]))
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue