Fix all clippy warnings - 0 warnings

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-28 14:27:52 -03:00
parent 96cf7b57f8
commit aeb4e8af0f
13 changed files with 220 additions and 309 deletions

View file

@ -1,10 +1,9 @@
use chrono::{DateTime, Duration, Utc};
use log::{debug, error, info, warn};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::fmt::Write;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, LazyLock, RwLock};
use uuid::Uuid;
const MAX_LOGS_PER_APP: usize = 500;
@ -446,7 +445,7 @@ impl Default for AppLogStore {
}
}
pub static APP_LOGS: Lazy<Arc<AppLogStore>> = Lazy::new(|| Arc::new(AppLogStore::new()));
pub static APP_LOGS: LazyLock<Arc<AppLogStore>> = LazyLock::new(|| Arc::new(AppLogStore::new()));
pub fn log_generator_info(app_name: &str, message: &str) {
APP_LOGS.log(

View file

@ -102,12 +102,9 @@ pub fn ask_later_keyword(state: Arc<AppState>, user: UserSession, engine: &mut E
}
});
let state_clone5 = state.clone();
let user_clone5 = user.clone();
engine.register_fn("list_pending_info", move || -> Dynamic {
let state = state_clone5.clone();
let user = user_clone5.clone();
let state = state.clone();
let user = user.clone();
match list_pending_info(&state, &user) {
Ok(items) => {

View file

@ -1,4 +1,4 @@
use crate::auto_task::auto_task::{
use crate::auto_task::task_types::{
AutoTask, AutoTaskStatus, ExecutionMode, PendingApproval, PendingDecision, TaskPriority,
};
use crate::auto_task::intent_classifier::IntentClassifier;
@ -1334,7 +1334,7 @@ fn create_auto_task_from_plan(
pending_decisions: Vec::new(),
pending_approvals: Vec::new(),
risk_summary: None,
resource_usage: crate::auto_task::auto_task::ResourceUsage::default(),
resource_usage: crate::auto_task::task_types::ResourceUsage::default(),
error: None,
rollback_state: None,
session_id: session.id.to_string(),
@ -1613,9 +1613,8 @@ fn update_task_status_db(
}
fn get_pending_items_for_bot(state: &Arc<AppState>, bot_id: Uuid) -> Vec<PendingItemResponse> {
let mut conn = match state.conn.get() {
Ok(c) => c,
Err(_) => return Vec::new(),
let Ok(mut conn) = state.conn.get() else {
return Vec::new();
};
#[derive(QueryableByName)]
@ -1860,18 +1859,15 @@ pub struct PendingItemsResponse {
pub async fn get_pending_items_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
info!("Getting pending items");
let session = match get_current_session(&state) {
Ok(s) => s,
Err(_) => {
return (
StatusCode::UNAUTHORIZED,
Json(PendingItemsResponse {
items: Vec::new(),
count: 0,
}),
)
.into_response();
}
let Ok(session) = get_current_session(&state) else {
return (
StatusCode::UNAUTHORIZED,
Json(PendingItemsResponse {
items: Vec::new(),
count: 0,
}),
)
.into_response();
};
let items = get_pending_items_for_bot(&state, session.bot_id);
@ -1898,18 +1894,15 @@ pub async fn submit_pending_item_handler(
) -> impl IntoResponse {
info!("Submitting pending item {item_id}: {}", request.value);
let session = match get_current_session(&state) {
Ok(s) => s,
Err(_) => {
return (
StatusCode::UNAUTHORIZED,
Json(serde_json::json!({
"success": false,
"error": "Authentication required"
})),
)
.into_response();
}
let Ok(session) = get_current_session(&state) else {
return (
StatusCode::UNAUTHORIZED,
Json(serde_json::json!({
"success": false,
"error": "Authentication required"
})),
)
.into_response();
};
match resolve_pending_item(&state, &item_id, &request.value, session.bot_id) {

View file

@ -6,7 +6,7 @@ use diesel::sql_query;
use diesel::sql_types::{Text, Uuid as DieselUuid};
use log::{info, trace, warn};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::sync::Arc;
use uuid::Uuid;
@ -164,7 +164,7 @@ impl DesignerAI {
line_number: None,
})
.collect(),
preview: Some(self.generate_preview(&analysis)),
preview: Some(Self::generate_preview(&analysis)),
requires_confirmation: true,
confirmation_message: analysis.confirmation_reason,
can_undo: true,
@ -174,10 +174,10 @@ impl DesignerAI {
}
// Apply the modification
self.apply_modification(&analysis, session).await
self.apply_modification(&analysis, session)
}
pub async fn apply_confirmed_modification(
pub fn apply_confirmed_modification(
&self,
change_id: &str,
session: &UserSession,
@ -186,7 +186,7 @@ impl DesignerAI {
let pending = self.get_pending_change(change_id, session)?;
match pending {
Some(analysis) => self.apply_modification(&analysis, session).await,
Some(analysis) => self.apply_modification(&analysis, session),
None => Ok(ModificationResult {
success: false,
modification_type: ModificationType::Unknown,
@ -202,7 +202,7 @@ impl DesignerAI {
}
}
pub async fn undo_change(
pub fn undo_change(
&self,
change_id: &str,
session: &UserSession,
@ -265,7 +265,7 @@ impl DesignerAI {
}
}
pub async fn get_context(
pub fn get_context(
&self,
session: &UserSession,
current_app: Option<&str>,
@ -335,11 +335,10 @@ Respond ONLY with valid JSON."#
);
let response = self.call_llm(&prompt).await?;
self.parse_analysis_response(&response, instruction)
Self::parse_analysis_response(&response, instruction)
}
fn parse_analysis_response(
&self,
response: &str,
instruction: &str,
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
@ -393,14 +392,12 @@ Respond ONLY with valid JSON."#
}
Err(e) => {
warn!("Failed to parse LLM analysis: {e}");
// Fallback to heuristic analysis
self.analyze_modification_heuristic(instruction)
Self::analyze_modification_heuristic(instruction)
}
}
}
fn analyze_modification_heuristic(
&self,
instruction: &str,
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
let lower = instruction.to_lowercase();
@ -461,7 +458,7 @@ Respond ONLY with valid JSON."#
})
}
async fn apply_modification(
fn apply_modification(
&self,
analysis: &AnalyzedModification,
session: &UserSession,
@ -476,25 +473,20 @@ Respond ONLY with valid JSON."#
// Generate new content based on modification type
let new_content = match analysis.modification_type {
ModificationType::Style => {
self.apply_style_changes(&original_content, &analysis.changes)
.await?
Self::apply_style_changes(&original_content, &analysis.changes)?
}
ModificationType::Html => {
self.apply_html_changes(&original_content, &analysis.changes)
.await?
Self::apply_html_changes(&original_content, &analysis.changes)?
}
ModificationType::Database => {
self.apply_database_changes(&original_content, &analysis.changes, session)
.await?
Self::apply_database_changes(&original_content, &analysis.changes)?
}
ModificationType::Tool => self.generate_tool_file(&analysis.changes, session).await?,
ModificationType::Tool => Self::generate_tool_file(&analysis.changes)?,
ModificationType::Scheduler => {
self.generate_scheduler_file(&analysis.changes, session)
.await?
Self::generate_scheduler_file(&analysis.changes)?
}
ModificationType::Multiple => {
// Handle multiple changes sequentially
self.apply_multiple_changes(analysis, session).await?
Self::apply_multiple_changes()?
}
ModificationType::Unknown => {
return Ok(ModificationResult {
@ -522,7 +514,7 @@ Respond ONLY with valid JSON."#
description: analysis.summary.clone(),
file_path: analysis.target_file.clone(),
original_content,
new_content: new_content.clone(),
new_content,
timestamp: Utc::now(),
can_undo: true,
};
@ -552,8 +544,7 @@ Respond ONLY with valid JSON."#
})
}
async fn apply_style_changes(
&self,
fn apply_style_changes(
original: &str,
changes: &[CodeChange],
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
@ -580,7 +571,7 @@ Respond ONLY with valid JSON."#
}
}
_ => {
content.push_str("\n");
content.push('\n');
content.push_str(&change.content);
}
}
@ -589,8 +580,7 @@ Respond ONLY with valid JSON."#
Ok(content)
}
async fn apply_html_changes(
&self,
fn apply_html_changes(
original: &str,
changes: &[CodeChange],
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
@ -627,11 +617,9 @@ Respond ONLY with valid JSON."#
Ok(content)
}
async fn apply_database_changes(
&self,
fn apply_database_changes(
original: &str,
changes: &[CodeChange],
session: &UserSession,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let mut content = original.to_string();
@ -654,28 +642,27 @@ Respond ONLY with valid JSON."#
content.push_str(&change.content);
}
_ => {
content.push_str("\n");
content.push('\n');
content.push_str(&change.content);
}
}
}
// Sync schema to database
self.sync_schema_changes(session)?;
Self::sync_schema_changes()?;
Ok(content)
}
async fn generate_tool_file(
&self,
fn generate_tool_file(
changes: &[CodeChange],
_session: &UserSession,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let mut content = String::new();
content.push_str(&format!(
let _ = write!(
content,
"' Tool generated by Designer\n' Created: {}\n\n",
Utc::now().format("%Y-%m-%d %H:%M")
));
);
for change in changes {
if !change.content.is_empty() {
@ -687,16 +674,15 @@ Respond ONLY with valid JSON."#
Ok(content)
}
async fn generate_scheduler_file(
&self,
fn generate_scheduler_file(
changes: &[CodeChange],
_session: &UserSession,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let mut content = String::new();
content.push_str(&format!(
let _ = write!(
content,
"' Scheduler generated by Designer\n' Created: {}\n\n",
Utc::now().format("%Y-%m-%d %H:%M")
));
);
for change in changes {
if !change.content.is_empty() {
@ -708,32 +694,28 @@ Respond ONLY with valid JSON."#
Ok(content)
}
async fn apply_multiple_changes(
&self,
_analysis: &AnalyzedModification,
_session: &UserSession,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// For multiple changes, each would be applied separately
// Return summary of changes
fn apply_multiple_changes() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
Ok("Multiple changes applied".to_string())
}
fn generate_preview(&self, analysis: &AnalyzedModification) -> String {
fn generate_preview(analysis: &AnalyzedModification) -> String {
let mut preview = String::new();
preview.push_str(&format!("File: {}\n\nChanges:\n", analysis.target_file));
let _ = writeln!(preview, "File: {}\n\nChanges:", analysis.target_file);
for (i, change) in analysis.changes.iter().enumerate() {
preview.push_str(&format!(
"{}. {} at '{}'\n",
let _ = writeln!(
preview,
"{}. {} at '{}'",
i + 1,
change.change_type,
change.target
));
);
if !change.content.is_empty() {
preview.push_str(&format!(
" New content: {}\n",
let _ = writeln!(
preview,
" New content: {}",
&change.content[..change.content.len().min(100)]
));
);
}
}
@ -746,21 +728,20 @@ Respond ONLY with valid JSON."#
) -> Result<Vec<TableInfo>, Box<dyn std::error::Error + Send + Sync>> {
let mut conn = self.state.conn.get()?;
// Query information_schema for tables in the bot's schema
let query = format!(
"SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
LIMIT 50"
);
#[derive(QueryableByName)]
struct TableRow {
#[diesel(sql_type = Text)]
table_name: String,
}
let tables: Vec<TableRow> = sql_query(&query).get_results(&mut conn).unwrap_or_default();
let tables: Vec<TableRow> = sql_query(
"SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
LIMIT 50",
)
.get_results(&mut conn)
.unwrap_or_default();
Ok(tables
.into_iter()
@ -783,7 +764,7 @@ Respond ONLY with valid JSON."#
if let Ok(entries) = std::fs::read_dir(&tools_path) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".bas") {
if name.to_lowercase().ends_with(".bas") {
tools.push(name.to_string());
}
}
@ -804,7 +785,7 @@ Respond ONLY with valid JSON."#
if let Ok(entries) = std::fs::read_dir(&schedulers_path) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".bas") {
if name.to_lowercase().ends_with(".bas") {
schedulers.push(name.to_string());
}
}
@ -920,10 +901,7 @@ Respond ONLY with valid JSON."#
Ok(())
}
fn sync_schema_changes(
&self,
_session: &UserSession,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
fn sync_schema_changes() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// This would trigger the TABLE keyword parser to sync
// For now, just log
info!("Schema changes need to be synced to database");

View file

@ -208,13 +208,13 @@ impl IntentClassifier {
match classification.intent_type {
IntentType::AppCreate => self.handle_app_create(classification, session).await,
IntentType::Todo => self.handle_todo(classification, session).await,
IntentType::Monitor => self.handle_monitor(classification, session).await,
IntentType::Todo => self.handle_todo(classification, session),
IntentType::Monitor => self.handle_monitor(classification, session),
IntentType::Action => self.handle_action(classification, session).await,
IntentType::Schedule => self.handle_schedule(classification, session).await,
IntentType::Goal => self.handle_goal(classification, session).await,
IntentType::Tool => self.handle_tool(classification, session).await,
IntentType::Unknown => self.handle_unknown(classification, session).await,
IntentType::Schedule => self.handle_schedule(classification, session),
IntentType::Goal => self.handle_goal(classification, session),
IntentType::Tool => self.handle_tool(classification, session),
IntentType::Unknown => Self::handle_unknown(classification),
}
}
@ -274,12 +274,10 @@ Respond with JSON only:
);
let response = self.call_llm(&prompt).await?;
self.parse_classification_response(&response, intent)
Self::parse_classification_response(&response, intent)
}
/// Parse LLM response into ClassifiedIntent
fn parse_classification_response(
&self,
response: &str,
original_intent: &str,
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
@ -380,14 +378,12 @@ Respond with JSON only:
}
Err(e) => {
warn!("Failed to parse LLM response, using heuristic: {e}");
self.classify_heuristic(original_intent)
Self::classify_heuristic(original_intent)
}
}
}
/// Fallback heuristic classification when LLM fails
fn classify_heuristic(
&self,
intent: &str,
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
let lower = intent.to_lowercase();
@ -459,11 +455,6 @@ Respond with JSON only:
})
}
// =========================================================================
// INTENT HANDLERS
// =========================================================================
/// Handle APP_CREATE: Generate full application with HTMX pages, tables, tools
async fn handle_app_create(
&self,
classification: &ClassifiedIntent,
@ -550,8 +541,7 @@ Respond with JSON only:
}
}
/// Handle TODO: Save task to tasks table
async fn handle_todo(
fn handle_todo(
&self,
classification: &ClassifiedIntent,
session: &UserSession,
@ -595,8 +585,7 @@ Respond with JSON only:
})
}
/// Handle MONITOR: Create ON CHANGE event handler
async fn handle_monitor(
fn handle_monitor(
&self,
classification: &ClassifiedIntent,
session: &UserSession,
@ -643,7 +632,7 @@ END ON
message: format!("Monitor created for: {subject}"),
created_resources: vec![CreatedResource {
resource_type: "event".to_string(),
name: handler_name.clone(),
name: handler_name,
path: Some(event_path),
}],
app_url: None,
@ -655,7 +644,6 @@ END ON
})
}
/// Handle ACTION: Execute immediately
async fn handle_action(
&self,
classification: &ClassifiedIntent,
@ -709,8 +697,7 @@ END ON
})
}
/// Handle SCHEDULE: Create SET SCHEDULE automation
async fn handle_schedule(
fn handle_schedule(
&self,
classification: &ClassifiedIntent,
session: &UserSession,
@ -783,8 +770,7 @@ END SCHEDULE
})
}
/// Handle GOAL: Create autonomous LLM loop with metrics
async fn handle_goal(
fn handle_goal(
&self,
classification: &ClassifiedIntent,
session: &UserSession,
@ -857,8 +843,7 @@ END GOAL
})
}
/// Handle TOOL: Create voice/chat command trigger
async fn handle_tool(
fn handle_tool(
&self,
classification: &ClassifiedIntent,
session: &UserSession,
@ -926,23 +911,20 @@ END TRIGGER
})
}
/// Handle UNKNOWN: Request clarification
async fn handle_unknown(
&self,
fn handle_unknown(
classification: &ClassifiedIntent,
_session: &UserSession,
) -> Result<IntentResult, Box<dyn std::error::Error + Send + Sync>> {
info!("Handling UNKNOWN intent - requesting clarification");
let suggestions = if !classification.alternative_types.is_empty() {
let suggestions = if classification.alternative_types.is_empty() {
"- Create an app\n- Add a task\n- Set up monitoring\n- Schedule automation".to_string()
} else {
classification
.alternative_types
.iter()
.map(|a| format!("- {}: {}", a.intent_type, a.reason))
.collect::<Vec<_>>()
.join("\n")
} else {
"- Create an app\n- Add a task\n- Set up monitoring\n- Schedule automation".to_string()
};
Ok(IntentResult {

View file

@ -1,12 +1,12 @@
pub mod app_generator;
pub mod app_logs;
pub mod ask_later;
pub mod auto_task;
pub mod autotask_api;
pub mod designer_ai;
pub mod intent_classifier;
pub mod intent_compiler;
pub mod safety_layer;
pub mod task_types;
pub use app_generator::{
AppGenerator, AppStructure, FileType, GeneratedApp, GeneratedFile, GeneratedPage, PageType,
@ -18,7 +18,6 @@ pub use app_logs::{
ClientLogRequest, LogLevel, LogQueryParams, LogSource, LogStats, APP_LOGS,
};
pub use ask_later::{ask_later_keyword, PendingInfoItem};
pub use auto_task::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
pub use autotask_api::{
apply_recommendation_handler, cancel_task_handler, classify_intent_handler,
compile_intent_handler, create_and_execute_handler, execute_plan_handler, execute_task_handler,
@ -28,6 +27,7 @@ pub use autotask_api::{
submit_pending_item_handler,
};
pub use designer_ai::DesignerAI;
pub use task_types::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
pub use intent_classifier::{ClassifiedIntent, IntentClassifier, IntentType};
pub use intent_compiler::{CompiledIntent, IntentCompiler};
pub use safety_layer::{AuditEntry, ConstraintCheckResult, SafetyLayer, SimulationResult};

View file

@ -35,18 +35,17 @@ pub async fn serve_app_index(
State(state): State<Arc<AppState>>,
Path(params): Path<AppPath>,
) -> impl IntoResponse {
serve_app_file_internal(&state, &params.app_name, "index.html").await
serve_app_file_internal(&state, &params.app_name, "index.html")
}
pub async fn serve_app_file(
State(state): State<Arc<AppState>>,
Path(params): Path<AppFilePath>,
) -> impl IntoResponse {
serve_app_file_internal(&state, &params.app_name, &params.file_path).await
serve_app_file_internal(&state, &params.app_name, &params.file_path)
}
async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &str) -> Response {
// Sanitize paths to prevent directory traversal
fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &str) -> Response {
let sanitized_app_name = sanitize_path_component(app_name);
let sanitized_file_path = sanitize_path_component(file_path);
@ -54,8 +53,6 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
}
// Construct full file path from SITE_ROOT
// Apps are synced to: {site_path}/{app_name}/
let site_path = state
.config
.as_ref()
@ -67,19 +64,16 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
site_path, sanitized_app_name, sanitized_file_path
);
trace!("Serving app file: {}", full_path);
trace!("Serving app file: {full_path}");
// Check if file exists
let path = std::path::Path::new(&full_path);
if !path.exists() {
warn!("App file not found: {}", full_path);
warn!("App file not found: {full_path}");
return (StatusCode::NOT_FOUND, "File not found").into_response();
}
// Determine content type
let content_type = get_content_type(&sanitized_file_path);
// Read and serve the file
match std::fs::read(&full_path) {
Ok(contents) => Response::builder()
.status(StatusCode::OK)
@ -94,7 +88,7 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
.into_response()
}),
Err(e) => {
error!("Failed to read file {}: {}", full_path, e);
error!("Failed to read file {full_path}: {e}");
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read file").into_response()
}
}
@ -109,13 +103,11 @@ pub async fn list_all_apps(State(state): State<Arc<AppState>>) -> impl IntoRespo
let mut apps = Vec::new();
// List all directories in site_path that have an index.html (are apps)
if let Ok(entries) = std::fs::read_dir(&site_path) {
for entry in entries.flatten() {
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
if let Some(name) = entry.file_name().to_str() {
// Skip .gbai directories and other system folders
if name.starts_with('.') || name.ends_with(".gbai") {
if name.starts_with('.') || name.to_lowercase().ends_with(".gbai") {
continue;
}

View file

@ -210,34 +210,28 @@ pub async fn get_record_handler(
let table_name = sanitize_identifier(&table);
let user_roles = user_roles_from_headers(&headers);
let record_id = match Uuid::parse_str(&id) {
Ok(uuid) => uuid,
Err(_) => {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response()
}
let Ok(record_id) = Uuid::parse_str(&id) else {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response();
};
let mut conn = match state.conn.get() {
Ok(c) => c,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(RecordResponse {
success: false,
data: None,
message: Some(format!("Database connection error: {e}")),
}),
)
.into_response()
}
let Ok(mut conn) = state.conn.get() else {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(RecordResponse {
success: false,
data: None,
message: Some("Database connection error".to_string()),
}),
)
.into_response();
};
// Check table-level read access
@ -314,19 +308,16 @@ pub async fn create_record_handler(
let table_name = sanitize_identifier(&table);
let user_roles = user_roles_from_headers(&headers);
let obj = match payload.as_object() {
Some(o) => o,
None => {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Payload must be a JSON object".to_string()),
}),
)
.into_response()
}
let Some(obj) = payload.as_object() else {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Payload must be a JSON object".to_string()),
}),
)
.into_response();
};
let mut columns: Vec<String> = vec!["id".to_string()];
@ -341,22 +332,18 @@ pub async fn create_record_handler(
values.push(value_to_sql(value));
}
let mut conn = match state.conn.get() {
Ok(c) => c,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(RecordResponse {
success: false,
data: None,
message: Some(format!("Database connection error: {e}")),
}),
)
.into_response()
}
let Ok(mut conn) = state.conn.get() else {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(RecordResponse {
success: false,
data: None,
message: Some("Database connection error".to_string()),
}),
)
.into_response();
};
// Check table-level write access
let access_info =
match check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
Ok(info) => info,
@ -438,34 +425,28 @@ pub async fn update_record_handler(
let table_name = sanitize_identifier(&table);
let user_roles = user_roles_from_headers(&headers);
let record_id = match Uuid::parse_str(&id) {
Ok(uuid) => uuid,
Err(_) => {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response()
}
let Ok(record_id) = Uuid::parse_str(&id) else {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response();
};
let obj = match payload.as_object() {
Some(o) => o,
None => {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Payload must be a JSON object".to_string()),
}),
)
.into_response()
}
let Some(obj) = payload.as_object() else {
return (
StatusCode::BAD_REQUEST,
Json(RecordResponse {
success: false,
data: None,
message: Some("Payload must be a JSON object".to_string()),
}),
)
.into_response();
};
let mut set_clauses: Vec<String> = Vec::new();
@ -588,37 +569,30 @@ pub async fn delete_record_handler(
let table_name = sanitize_identifier(&table);
let user_roles = user_roles_from_headers(&headers);
let record_id = match Uuid::parse_str(&id) {
Ok(uuid) => uuid,
Err(_) => {
return (
StatusCode::BAD_REQUEST,
Json(DeleteResponse {
success: false,
deleted: 0,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response()
}
let Ok(record_id) = Uuid::parse_str(&id) else {
return (
StatusCode::BAD_REQUEST,
Json(DeleteResponse {
success: false,
deleted: 0,
message: Some("Invalid UUID format".to_string()),
}),
)
.into_response();
};
let mut conn = match state.conn.get() {
Ok(c) => c,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(DeleteResponse {
success: false,
deleted: 0,
message: Some(format!("Database connection error: {e}")),
}),
)
.into_response()
}
let Ok(mut conn) = state.conn.get() else {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(DeleteResponse {
success: false,
deleted: 0,
message: Some("Database connection error".to_string()),
}),
)
.into_response();
};
// Check table-level write access (delete requires write)
if let Err(e) = check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
return (
StatusCode::FORBIDDEN,

View file

@ -99,10 +99,10 @@ impl UserRoles {
}
// Also check if user is marked as admin in context
if let Some(Value::Bool(true)) = session.context_data.get("is_admin") {
if !roles.contains(&"admin".to_string()) {
roles.push("admin".to_string());
}
if matches!(session.context_data.get("is_admin"), Some(Value::Bool(true)))
&& !roles.contains(&"admin".to_string())
{
roles.push("admin".to_string());
}
Self {
@ -224,26 +224,21 @@ pub fn load_table_access_info(
.bind::<Text, _>(table_name)
.get_result(conn);
let table_def = match table_result {
Ok(row) => row,
Err(_) => {
trace!(
"No table definition found for '{}', allowing open access",
table_name
);
return None; // No definition = open access
}
let Ok(table_def) = table_result else {
trace!(
"No table definition found for '{table_name}', allowing open access"
);
return None;
};
let mut info = TableAccessInfo {
table_name: table_def.table_name,
read_roles: parse_roles_string(&table_def.read_roles),
write_roles: parse_roles_string(&table_def.write_roles),
read_roles: parse_roles_string(table_def.read_roles.as_ref()),
write_roles: parse_roles_string(table_def.write_roles.as_ref()),
field_read_roles: HashMap::new(),
field_write_roles: HashMap::new(),
};
// Query field-level permissions
let fields_result: Result<Vec<FieldDefRow>, _> = sql_query(
"SELECT f.field_name, f.read_roles, f.write_roles
FROM dynamic_table_fields f
@ -255,8 +250,8 @@ pub fn load_table_access_info(
if let Ok(fields) = fields_result {
for field in fields {
let field_read = parse_roles_string(&field.read_roles);
let field_write = parse_roles_string(&field.write_roles);
let field_read = parse_roles_string(field.read_roles.as_ref());
let field_write = parse_roles_string(field.write_roles.as_ref());
if !field_read.is_empty() {
info.field_read_roles
@ -279,9 +274,8 @@ pub fn load_table_access_info(
Some(info)
}
fn parse_roles_string(roles: &Option<String>) -> Vec<String> {
fn parse_roles_string(roles: Option<&String>) -> Vec<String> {
roles
.as_ref()
.map(|s| {
s.split(';')
.map(|r| r.trim().to_string())

View file

@ -286,8 +286,6 @@ fn parse_field_definition(
reference_table = Some(parts[i + 1].to_string());
}
}
// Skip READ, BY, WRITE as they're handled separately
"read" | "by" | "write" => {}
_ => {}
}
}

View file

@ -244,7 +244,7 @@ pub async fn ensure_crawler_service_running(
Arc::clone(kb_manager),
));
let _ = service.start();
drop(service.start());
info!("Website crawler service initialized");

View file

@ -10,6 +10,8 @@ use axum::{
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::path::Path;
use std::sync::Arc;
use uuid::Uuid;
@ -1096,13 +1098,14 @@ fn build_designer_prompt(request: &DesignerModifyRequest) -> String {
.map(|ctx| {
let mut info = String::new();
if let Some(ref html) = ctx.page_html {
info.push_str(&format!(
"\nCurrent page HTML (first 500 chars):\n{}\n",
let _ = writeln!(
info,
"\nCurrent page HTML (first 500 chars):\n{}",
&html[..html.len().min(500)]
));
);
}
if let Some(ref tables) = ctx.tables {
info.push_str(&format!("\nAvailable tables: {}\n", tables.join(", ")));
let _ = writeln!(info, "\nAvailable tables: {}", tables.join(", "));
}
info
})
@ -1226,7 +1229,7 @@ async fn parse_and_apply_changes(
code: Option<String>,
}
let parsed: LlmChangeResponse = serde_json::from_str(llm_response).unwrap_or(LlmChangeResponse {
let parsed: LlmChangeResponse = serde_json::from_str(llm_response).unwrap_or_else(|_| LlmChangeResponse {
_understanding: Some("Could not parse LLM response".to_string()),
changes: None,
message: Some("I understood your request but encountered an issue processing it. Could you try rephrasing?".to_string()),
@ -1324,15 +1327,16 @@ async fn apply_file_change(
}
fn get_content_type(filename: &str) -> &'static str {
if filename.ends_with(".html") {
"text/html"
} else if filename.ends_with(".css") {
"text/css"
} else if filename.ends_with(".js") {
"application/javascript"
} else if filename.ends_with(".json") {
"application/json"
} else {
"text/plain"
match Path::new(filename)
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.as_deref()
{
Some("html") => "text/html",
Some("css") => "text/css",
Some("js") => "application/javascript",
Some("json") => "application/json",
_ => "text/plain",
}
}